diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml
deleted file mode 100644
index 55b11947a..000000000
--- a/.github/workflows/docs-check.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: docs-check
-
-on:
- pull_request:
- branches: ["main"]
- paths:
- - "docs/**"
- - "mkdocs.yml"
- - "requirements-docs.txt"
- - "README.md"
- - ".github/workflows/docs*.yml"
- push:
- branches: ["dev", "main"]
- paths:
- - "docs/**"
- - "mkdocs.yml"
- - "requirements-docs.txt"
- - "README.md"
- - ".github/workflows/docs*.yml"
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-python@v5
- with:
- python-version: "3.x"
-
- - name: Install docs dependencies
- run: python -m pip install -r requirements-docs.txt
-
- - name: Build docs
- run: python -m mkdocs build --strict
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
deleted file mode 100644
index 87d093273..000000000
--- a/.github/workflows/docs.yml
+++ /dev/null
@@ -1,77 +0,0 @@
-name: docs
-
-on:
- push:
- branches: ["main"]
- workflow_dispatch:
- inputs:
- version:
- description: "Version to deploy (e.g. 1.10.0 or dev)"
- required: true
- default: "dev"
- alias:
- description: "Alias to update (optional)"
- required: false
- set_default:
- description: "Set version as default (true/false)"
- required: false
- default: "false"
-
-permissions:
- contents: write
-
-concurrency:
- group: gh-pages
- cancel-in-progress: true
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - uses: actions/setup-python@v5
- with:
- python-version: "3.x"
-
- - name: Install docs dependencies
- run: python -m pip install -r requirements-docs.txt
-
- - name: Validate docs build
- run: python -m mkdocs build --strict
-
- - name: Configure git
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
-
- - name: Deploy docs (tag)
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
- run: |
- VERSION="${GITHUB_REF_NAME#v}"
- MIKE_VERSION="$VERSION" mike deploy --push --update-aliases "$VERSION" stable
- mike set-default --push stable
-
- - name: Deploy docs (main)
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- run: |
- MIKE_VERSION="dev" mike deploy --push --update-aliases dev latest
- mike set-default --push latest
-
- - name: Deploy docs (manual)
- if: github.event_name == 'workflow_dispatch'
- run: |
- VERSION="${{ inputs.version }}"
- if [ -n "${{ inputs.alias }}" ]; then
- MIKE_VERSION="$VERSION" mike deploy --push --update-aliases "$VERSION" "${{ inputs.alias }}"
- else
- MIKE_VERSION="$VERSION" mike deploy --push "$VERSION"
- fi
- if [ "${{ inputs.set_default }}" = "true" ]; then
- mike set-default --push "$VERSION"
- fi
diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml
deleted file mode 100644
index 9a5f2ba33..000000000
--- a/.github/workflows/javadoc.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: javadoc
-
-on:
- workflow_dispatch:
- inputs:
- version:
- description: "Version to deploy (e.g. 1.10.0 or dev)"
- required: true
- default: "dev"
-
-permissions:
- contents: write
-
-concurrency:
- group: gh-pages
- cancel-in-progress: true
-
-jobs:
- publish:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - uses: actions/setup-java@v5
- with:
- distribution: "temurin"
- java-version: "25"
- cache: "gradle"
-
- - uses: gradle/actions/setup-gradle@v3
-
- - name: Resolve Javadoc version
- run: |
- if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
- VERSION="dev"
- elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/v* ]]; then
- VERSION="${GITHUB_REF_NAME#v}"
- elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- VERSION="${{ inputs.version }}"
- else
- VERSION="dev"
- fi
- echo "JAVADOC_VERSION=$VERSION" >> "$GITHUB_ENV"
-
- - name: Build Javadoc
- run: |
- bash ./gradlew javadoc
- OUT="$RUNNER_TEMP/javadoc"
- rm -rf "$OUT"
- mkdir -p "$OUT"
- for dir in */build/docs/javadoc; do
- [ -d "$dir" ] || continue
- module="$(echo "$dir" | cut -d/ -f1)"
- cp -R "$dir" "$OUT/$module"
- done
- {
- echo ''
- echo '
MagicUtils Javadoc'
- echo "MagicUtils Javadoc ${JAVADOC_VERSION}
"
- echo ''
- for module in $(ls "$OUT"); do
- [ "$module" = "index.html" ] && continue
- echo "- ${module}
"
- done
- echo '
'
- } > "$OUT/index.html"
-
- - name: Publish Javadoc
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
- git fetch origin gh-pages
- git checkout gh-pages
- DEST="javadoc/${JAVADOC_VERSION}"
- rm -rf "$DEST"
- mkdir -p "$DEST"
- cp -R "$RUNNER_TEMP/javadoc/"* "$DEST/"
- git add "$DEST"
- if git diff --cached --quiet; then
- echo "No Javadoc changes to publish."
- exit 0
- fi
- git commit -m "docs: publish javadoc ${JAVADOC_VERSION}"
- git push origin gh-pages
diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml
index 38a5676df..4ff31b497 100644
--- a/.github/workflows/publish-maven.yml
+++ b/.github/workflows/publish-maven.yml
@@ -1,9 +1,9 @@
name: publish-maven
on:
- workflow_run:
- workflows: ["docs"]
- types: [completed]
+ # Dispatched by release.yml after the tag is created (documentation moved to
+ # the separate MagicUtilsWebsite project, so there is no docs workflow to chain
+ # from anymore). Also runnable manually.
workflow_dispatch:
# Publishing targets the self-hosted Reposilite (maven.theroer.dev) over HTTPS,
@@ -21,14 +21,13 @@ jobs:
# one-line edit to targets.properties — this workflow never lists targets.
resolve-matrix:
runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
outputs:
units: ${{ steps.matrix.outputs.units }}
repo_url: ${{ steps.coords.outputs.repo_url }}
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ github.event.workflow_run.head_sha || github.sha }}
+ ref: ${{ github.sha }}
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
@@ -61,7 +60,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ github.event.workflow_run.head_sha || github.sha }}
+ ref: ${{ github.sha }}
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
@@ -88,7 +87,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ github.event.workflow_run.head_sha || github.sha }}
+ ref: ${{ github.sha }}
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index efad4be81..45c5723a3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -88,20 +88,16 @@ jobs:
runs-on: ubuntu-latest
needs: [resolve, tag]
steps:
- - name: Dispatch docs and javadoc workflows
+ - name: Dispatch Maven publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.resolve.outputs.version }}"
TAG="${{ needs.resolve.outputs.tag }}"
- # Use workflow_dispatch (not the tag push) to launch docs.yml.
- # This avoids GitHub's GITHUB_TOKEN-pushed-tag rule that suppresses
- # downstream workflow_run events — publish-maven.yml will then see
- # docs as a workflow_dispatch source and trigger correctly.
- gh workflow run docs.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \
- -f version="$VERSION" \
- -f alias=stable \
- -f set_default=true
- gh workflow run javadoc.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \
+ # Documentation lives in the separate MagicUtilsWebsite (Nuxt) project,
+ # so the release only dispatches the Maven publish. Use workflow_dispatch
+ # (not the tag push) to avoid GitHub's GITHUB_TOKEN-pushed-tag rule that
+ # suppresses downstream workflow_run events.
+ gh workflow run publish-maven.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \
-f version="$VERSION"
- echo "Dispatched docs.yml and javadoc.yml for $TAG"
+ echo "Dispatched publish-maven.yml for $TAG"
diff --git a/README.md b/README.md
index 07f093229..2dabd7c19 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,10 @@ dependencies {
}
```
-Shared bundle install:
+Standalone install: `magicutils-fabric-bundle` is also a runnable Fabric mod on
+its own — drop the jar into `mods/` and it boots the shared MagicUtils runtime
+and registers the `/magicutils` (alias `/mu`) command with diagnostics
+sub-commands, no host mod required. Depend on it from your mod with:
```kotlin
dependencies {
@@ -124,8 +127,9 @@ https://magicutils.theroer.dev/getting-started/quickstart/
## Notes
-- GitHub Pages Maven does not host `*-all` shaded artifacts. Use the thin jars
- from Maven or build shaded jars locally when you need them.
+- Artifacts are published to the self-hosted Reposilite at
+ `https://maven.theroer.dev/releases`. Both the thin jars and the `*-all`
+ shaded jars (where produced) are available there.
## Reflection Automation
@@ -142,14 +146,14 @@ new reflection usage automatically.
## Release Helper
-Maintainers can prepare and dispatch a tagged release with:
+Maintainers cut a tagged release with the Gradle release tasks (the former
+`scripts/publish_release.py` is deprecated):
```bash
-python3 scripts/publish_release.py 1.19.2 --dry-run
-python3 scripts/publish_release.py 1.19.2
+./gradlew releasePreflight -Pversion=X.Y.Z # validate version + tags, no changes
+./gradlew release -Pversion=X.Y.Z # preflight → bump → dispatch release.yml
```
-The helper mirrors the GitHub Actions flow: it can sync `gradle.properties`,
-push the selected branch, wait until `origin/[` resolves to the pushed
-commit, and then dispatch `release.yml`. Optional flags are available for
-manual `javadoc.yml` and `publish-maven.yml` dispatches when needed.
+`release` validates the version, bumps `gradle.properties`, commits, and
+dispatches `release.yml`. CI then tags `vX.Y.Z`, builds docs/javadoc, and
+publishes the artifacts to Reposilite. See `RELEASING.md` for the full flow.
diff --git a/RELEASING.md b/RELEASING.md
index 8ca3c3f90..df4ba5c9f 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -12,8 +12,8 @@ land on `dev`, then `dev` is fast-forwarded into `main` before a release.
`release` chains `releasePreflight` → `bumpVersion` → `dispatchRelease`:
it validates the version, bumps `gradle.properties` and commits, then
-dispatches `release.yml`. The server-side chain (tag, docs/javadoc,
-gh-pages publish) runs from CI. Verify afterwards with `./gradlew
+dispatches `release.yml`. The server-side chain (tag, Reposilite Maven
+publish) runs from CI. Verify afterwards with `./gradlew
smokeTest -Pversion=1.21.4` (polls the published POM).
## Release tasks
@@ -89,40 +89,29 @@ release.yml
├── resolve version + tag string outputs
├── tag git tag vX.Y.Z && git push origin vX.Y.Z
└── dispatch-downstream
- gh workflow run docs.yml --ref vX.Y.Z -f alias=stable -f set_default=true
- gh workflow run javadoc.yml --ref vX.Y.Z
+ gh workflow run publish-maven.yml --ref vX.Y.Z -f version=X.Y.Z
-docs.yml (from workflow_dispatch)
- └── mkdocs build → mike deploy → push gh-pages
-
-publish-maven.yml (workflow_run after docs success)
+publish-maven.yml (from workflow_dispatch)
├── resolve-matrix ./gradlew printPublishMatrix (targets from targets.properties)
├── publish (matrix) ./gradlew -Ptarget= → PUT into Reposilite releases
└── publish-plugins ./gradlew -p build-logic publish → Reposilite releases
-javadoc.yml (from workflow_dispatch)
- └── javadoc per module → push gh-pages under javadoc/X.Y.Z/
-
verify (manual)
└── ./gradlew smokeTest -Pversion=X.Y.Z
HEAD-polls https://maven.theroer.dev/releases/.../magicutils-core-X.Y.Z.pom
```
-The chain is fully automatic. If a previous release used the manual
-`--dispatch-publish-maven` flag, you no longer need it — release.yml's
-`dispatch-downstream` job replaces that fallback.
+Documentation is not part of this pipeline — the docs site lives in the separate
+MagicUtilsWebsite (Nuxt) project. Javadoc jars are published to Reposilite
+alongside the artifacts.
## Why workflow_dispatch instead of tag-push triggers
-GitHub suppresses downstream workflows whose source push was made with
-the runner's `GITHUB_TOKEN`. The tag we push from `release.yml` is one
-of those, so a `workflow_run` trigger that listens for the docs build
-will not fire if docs were started by the tag push.
-
-`release.yml` instead calls `gh workflow run docs.yml` from
-`dispatch-downstream`. That is a `workflow_dispatch` event, which has
-no such restriction — `publish-maven.yml` then fires through
-`workflow_run` as designed.
+GitHub suppresses downstream workflows whose source push was made with the
+runner's `GITHUB_TOKEN`. The tag pushed from `release.yml` is one of those, so a
+`workflow_run` trigger would not fire. Instead `release.yml`'s
+`dispatch-downstream` job calls `gh workflow run publish-maven.yml` directly —
+a `workflow_dispatch` event, which has no such restriction.
## Branch model
@@ -138,8 +127,8 @@ no such restriction — `publish-maven.yml` then fires through
|-------|---------|--------|
| `validate` | `./gradlew buildScenario` fails | Fix the failing tests on `main`, then re-run `./gradlew release` with the same version. |
| `tag` | "Tag vX.Y.Z already exists" | Either bump to a new patch number or delete the stale tag remotely (`git push origin :refs/tags/vX.Y.Z`) and re-run. |
-| `dispatch-downstream` | docs/javadoc not started | Re-run the failed job from the Actions UI; gh CLI: `gh workflow run docs.yml --ref vX.Y.Z -f version=X.Y.Z -f alias=stable -f set_default=true`. |
-| `publish-maven` | Maven artifact missing | Inspect `gh run list -w publish-maven.yml`. The job uses `workflow_run` from docs success — if docs failed, fix and rerun docs first. Manual fallback: `gh workflow run publish-maven.yml`. |
+| `dispatch-downstream` | publish-maven not started | Re-run the failed job from the Actions UI; gh CLI: `gh workflow run publish-maven.yml --ref vX.Y.Z -f version=X.Y.Z`. |
+| `publish-maven` | Maven artifact missing | Inspect `gh run list -w publish-maven.yml`. Manual run: `gh workflow run publish-maven.yml -f version=X.Y.Z`. Check the `MAVEN_PUBLISH_USER`/`MAVEN_PUBLISH_TOKEN` secrets are set. |
| smoke poll timeout | `404` persists | The publish job failed or its credentials were wrong. Check `gh run list -w publish-maven.yml` and that the `MAVEN_PUBLISH_USER`/`MAVEN_PUBLISH_TOKEN` secrets are set. Reposilite serves artifacts immediately (no CDN lag) — a lasting 404 means it wasn't uploaded. |
To roll back a release that was tagged but not yet usable:
@@ -157,9 +146,7 @@ usually isn't worth removing — bump the next version instead.
```bash
# Was the chain successful?
gh run list -R THEROER/MagicUtils -w release.yml --limit 1
-gh run list -R THEROER/MagicUtils -w docs.yml --limit 1
gh run list -R THEROER/MagicUtils -w publish-maven.yml --limit 1
-gh run list -R THEROER/MagicUtils -w javadoc.yml --limit 1
# Is the artifact visible?
curl -fI https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-lang/X.Y.Z/magicutils-lang-X.Y.Z.pom
@@ -192,6 +179,5 @@ it for genuine emergencies.
- `build-logic/src/main/kotlin/MagicUtilsReleaseTasks.kt` — the `release`
group tasks (pure logic in `MagicUtilsReleaseModel.kt`).
- `.github/workflows/release.yml` — orchestrator.
-- `.github/workflows/docs.yml`, `publish-maven.yml`, `javadoc.yml` — the
- three downstream workflows.
+- `.github/workflows/publish-maven.yml` — the downstream Maven publish.
- `scripts/README.md` — short pointer back here.
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
index b4c6869bc..69601af81 100644
--- a/build-logic/build.gradle.kts
+++ b/build-logic/build.gradle.kts
@@ -19,13 +19,26 @@ repositories {
}
publishing {
- // Publish to the same gh-pages Maven repo as the library when -Ppublish_repo
+ // Publish to the same Reposilite Maven repo as the library when -Ppublish_repo
// is set (CI); always available via mavenLocal for dogfooding/consumers.
+ // This build script can't use the magicUtilsPublishRepository helper (that's
+ // part of the plugins it builds), so it mirrors the same credential handling:
+ // user/password from PUBLISH_USER/PUBLISH_TOKEN, applied only when supplied.
if (project.hasProperty("publish_repo")) {
+ val publishUser = (findProperty("publish_user") as? String)?.takeIf { it.isNotBlank() }
+ ?: System.getenv("PUBLISH_USER")?.takeIf { it.isNotBlank() }
+ val publishPassword = (findProperty("publish_password") as? String)?.takeIf { it.isNotBlank() }
+ ?: System.getenv("PUBLISH_TOKEN")?.takeIf { it.isNotBlank() }
repositories {
maven {
- name = "ghPages"
+ name = "magicutilsPublish"
url = uri(project.property("publish_repo") as String)
+ if (publishUser != null && publishPassword != null) {
+ credentials {
+ username = publishUser
+ password = publishPassword
+ }
+ }
}
}
}
diff --git a/docs/api.md b/docs/api.md
deleted file mode 100644
index d2deaca1d..000000000
--- a/docs/api.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# API reference
-
-The API reference is provided as Javadoc.
-
-- Local build: `./gradlew javadoc`
-- Hosted docs (releases): `https://magicutils.theroer.dev/javadoc/{{ magicutils_version }}/`
-
-If you are on a dev build, the hosted Javadoc may not exist yet.
diff --git a/docs/contributing.md b/docs/contributing.md
deleted file mode 100644
index 44b5bb236..000000000
--- a/docs/contributing.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Contributing
-
-## Build
-
-```bash
-./gradlew build
-```
-
-## Build the docs locally
-
-```bash
-python -m pip install -r requirements-docs.txt
-mkdocs build --strict
-mkdocs serve
-```
-
-Preferred release/tag flow:
-
-```bash
-python3 scripts/publish_release.py 1.10.0 --dry-run
-python3 scripts/publish_release.py 1.10.0
-```
-
-## Deploy versioned docs (maintainers)
-
-Manual docs deployment:
-
-```bash
-mike deploy --update-aliases 1.10.0 stable
-mike set-default stable
-```
-
-For dev docs:
-
-```bash
-mike deploy --update-aliases dev latest
-mike set-default latest
-```
diff --git a/docs/faq.md b/docs/faq.md
deleted file mode 100644
index 846e5f528..000000000
--- a/docs/faq.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# FAQ / Troubleshooting
-
-Common issues and fixes when integrating MagicUtils.
-
-## YAML/TOML config not available
-
-If you see `YAMLFactory`/`TOMLFactory` missing, add the format helpers:
-
-- `magicutils-config-yaml`
-- `magicutils-config-toml`
-
-If the config format is not required, switch to JSON/JSONC instead.
-
-## Jackson `NoSuchMethodError`
-
-Errors like `YAMLParser.createChildObjectContext(...)` or
-`BufferRecycler.releaseToPool()` indicate mixed Jackson versions. Make sure
-your project uses the same Jackson version as MagicUtils (currently `2.17.1`)
-and exclude older transitive Jackson artifacts.
-
-## `NoClassDefFoundError: dev.ua.theroer.magicutils.platform.Platform`
-
-The platform API is missing from the runtime. Add `magicutils-api` (or use a
-platform bundle such as `magicutils-bukkit` / `magicutils-fabric-bundle`).
-
-## `NoClassDefFoundError` for Adventure classes
-
-Missing classes like `net.kyori.ansi.StyleOps` or
-`net.kyori.adventure.text.serializer.json.JSONComponentSerializer` mean the
-Adventure serializer dependencies were not included. Use the platform bundle
-or add the required Adventure modules explicitly.
-
-## Placeholders do not resolve
-
-MagicUtils bridges to external placeholder mods/plugins only when they are
-installed:
-
-- Bukkit: [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi)
-- Fabric: [PB4 Placeholder API](https://placeholders.pb4.eu/)
-- Fabric: [Text Placeholder API](https://modrinth.com/mod/placeholder-api) or
- [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders)
-
-## Which artifact should I use?
-
-For most users:
-
-- Bukkit/Paper plugin: `magicutils-bukkit`
-- Fabric mod: `magicutils-fabric-bundle`
-- Velocity plugin: `magicutils-velocity`
-- NeoForge mod: `magicutils-neoforge` plus `magicutils-commands-neoforge`
-
-Use the modular artifacts only when you intentionally want custom wiring.
-
-## Permissions on Fabric
-
-Without `fabric-permissions-api`, MagicUtils falls back to op-level checks.
-Install the permissions API (and a permissions plugin like LuckPerms) to get
-full permission support.
-
-## Do I need to call `magic.runtime().close()`?
-
-Yes, if you built your services through `buildRuntime()` and your platform has a
-clear shutdown phase. Closing the runtime unregisters hooks, closes managed
-resources, and shuts down the config manager when it owns it.
-
-## Why do Fabric commands still register in `CommandRegistrationCallback`?
-
-Because Brigadier registration still belongs to Fabric's command callback.
-`FabricBootstrap` prepares the shared services and command registry, but the
-dispatcher itself only exists inside the event callback.
-
-## `Logger` or `LoggerCore`?
-
-Use:
-
-- `Logger` on Bukkit and Fabric
-- `LoggerCore` on Velocity, NeoForge, and custom platforms
-
-Bootstrap helpers return the correct logger type for each platform.
-
-## Config watcher warnings
-
-If you see `Failed to register config watcher` / `inotify` warnings, your OS
-file watch limit is too low. MagicUtils disables realtime reload in this
-case. Increase the limit or call `ConfigManager.shutdown()` on plugin disable
-to free watchers.
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
deleted file mode 100644
index 28a877ab4..000000000
--- a/docs/getting-started/installation.md
+++ /dev/null
@@ -1,216 +0,0 @@
-# Installation
-
-MagicUtils is split into modules. For most projects you only need one platform
-entry point (`magicutils-bukkit`, `magicutils-fabric-bundle`,
-`magicutils-velocity`, or `magicutils-neoforge`) plus optional format helpers.
-
-Use the modular artifacts only when you need a custom wiring path.
-
-## Choose A Version
-
-This documentation is versioned. The examples below use
-`{{ magicutils_version }}` which matches the current docs version.
-
-If you want to hardcode it in your build, replace it with the exact number
-(for example `1.10.0`).
-
-## Repositories
-
-Add the GitHub Pages Maven repository.
-
-```kotlin
-repositories {
- maven("https://maven.theroer.dev/releases/")
-}
-```
-
-## Which Artifact Do I Need?
-
-| Scenario | Recommended artifacts | Notes |
-| --- | --- | --- |
-| Bukkit/Paper plugin | `magicutils-bukkit` | Default choice for most plugins. |
-| Shared Bukkit server install | `magicutils-bukkit-bundle` + plugin `compileOnly` dependency | Use when multiple plugins should share one install. |
-| Fabric mod | `magicutils-fabric-bundle` | Default choice for most mods. |
-| Shared Fabric server install | `magicutils-fabric-bundle:dev` without `include(...)` | Install the bundle mod on the server. |
-| Modular Fabric setup | `magicutils-fabric` + Fabric integration modules | Use only when you want custom wiring. |
-| Velocity plugin | `magicutils-velocity` | Includes config/logger/lang/commands support. |
-| NeoForge mod | `magicutils-neoforge` + `magicutils-commands-neoforge` | Placeholder bridge is not available yet. |
-| Extra config formats | `magicutils-config-yaml`, `magicutils-config-toml` | Optional helpers for YAML and TOML. |
-| HTTP client | `magicutils-http-client` | Optional runtime-aware HTTP/WebSocket clients. |
-
-## Bukkit/Paper
-
-The Bukkit/Paper adapter bundles the core modules (config, logger, commands,
-lang, placeholders). Add optional format helpers only when you need them.
-
-You can use it in two ways:
-
-1. Embed MagicUtils into your plugin.
-2. Use a shared MagicUtils plugin (`magicutils-bukkit-bundle`) so multiple
- plugins reuse the same runtime.
-
-Kotlin DSL:
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}")
- implementation("dev.ua.theroer:magicutils-config-yaml:{{ magicutils_version }}")
- implementation("dev.ua.theroer:magicutils-config-toml:{{ magicutils_version }}")
-}
-```
-
-Groovy DSL:
-
-```groovy
-dependencies {
- implementation 'dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}'
- implementation 'dev.ua.theroer:magicutils-config-yaml:{{ magicutils_version }}'
- implementation 'dev.ua.theroer:magicutils-config-toml:{{ magicutils_version }}'
-}
-```
-
-### Shared Bukkit Bundle
-
-If you want a single shared MagicUtils install for multiple plugins, use the
-bundle plugin and do not embed MagicUtils inside your plugins.
-
-Dependencies (compile-only):
-
-```kotlin
-dependencies {
- compileOnly("dev.ua.theroer:magicutils-bukkit-bundle:{{ magicutils_version }}")
-}
-```
-
-Install the bundle on the server:
-
-- Drop `magicutils-bukkit-bundle-{{ magicutils_version }}.jar` into `plugins/`.
-- Add `depend: [MagicUtils]` (or `softdepend`) to your `plugin.yml`.
-
-## Fabric
-
-You have two options:
-
-### Embed The Bundle Inside Your Mod
-
-This is the recommended approach for single-mod setups and avoids requiring
-server owners to install MagicUtils separately.
-
-Kotlin DSL:
-
-```kotlin
-dependencies {
- modImplementation(include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}"))
- modCompileOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev")
- modRuntimeOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev")
-}
-```
-
-Groovy DSL:
-
-```groovy
-dependencies {
- modImplementation(include('dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}'))
- modCompileOnly 'dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev'
- modRuntimeOnly 'dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev'
-}
-```
-
-### Depend On A Shared Bundle Mod
-
-If you want one shared MagicUtils install for multiple mods, use a standard
-dependency and install the bundle mod on the server.
-
-```kotlin
-dependencies {
- modImplementation("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev")
-}
-```
-
-If you pick the shared bundle, add the `magicutils-fabric-bundle` mod to the
-server `mods/` folder and do not embed it inside other mods.
-
-You can download the bundle from the Maven repository:
-[`magicutils-fabric-bundle-{{ magicutils_version }}.jar`](https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-fabric-bundle/{{ magicutils_version }}/magicutils-fabric-bundle-{{ magicutils_version }}.jar)
-
-You can also add a dependency in your `fabric.mod.json`:
-
-```json
-{
- "depends": {
- "magicutils-fabric-bundle": ">={{ magicutils_version }}"
- }
-}
-```
-
-### Modular Fabric Dependencies
-
-Use this only when you want to wire specific modules yourself instead of using
-the bundle. `FabricBootstrap` lives in the command integration layer, so a
-bootstrap-first modular setup usually pulls both the platform adapter and the
-Fabric integration modules.
-
-```kotlin
-dependencies {
- modImplementation("dev.ua.theroer:magicutils-fabric:{{ magicutils_version }}:dev")
- modImplementation("dev.ua.theroer:magicutils-logger-fabric:{{ magicutils_version }}:dev")
- modImplementation("dev.ua.theroer:magicutils-commands-fabric:{{ magicutils_version }}:dev")
- modImplementation("dev.ua.theroer:magicutils-placeholders-fabric:{{ magicutils_version }}:dev")
- modImplementation("dev.ua.theroer:magicutils-config:{{ magicutils_version }}")
- modImplementation("dev.ua.theroer:magicutils-lang:{{ magicutils_version }}")
-}
-```
-
-## NeoForge
-
-NeoForge exposes platform, config, logger, and Brigadier command integrations.
-There is no NeoForge placeholder bridge yet.
-
-Kotlin DSL:
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}")
- implementation("dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}")
-}
-```
-
-Groovy DSL:
-
-```groovy
-dependencies {
- implementation 'dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}'
- implementation 'dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}'
-}
-```
-
-## Velocity
-
-The Velocity adapter exposes platform, config, logger, lang, and command
-integration in a single artifact.
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}")
-}
-```
-
-## Optional Format Helpers
-
-- `magicutils-config-yaml` enables YAML support.
-- `magicutils-config-toml` enables TOML support.
-
-Without them, MagicUtils uses JSON or JSONC (Fabric default).
-
-## Optional HTTP Client
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-http-client:{{ magicutils_version }}")
-}
-```
-
-## Notes On Shaded Artifacts
-
-Local builds produce `*-all` artifacts (shaded). The GitHub Pages repository
-does not include these files due to the 100 MB limit, so do not depend on them.
diff --git a/docs/getting-started/migration.md b/docs/getting-started/migration.md
deleted file mode 100644
index 1915707cf..000000000
--- a/docs/getting-started/migration.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# Migration Guide
-
-This page shows how to move from the older manual wiring style to the current
-bootstrap-first setup.
-
-## 1. Manual Bootstrap -> `buildRuntime()`
-
-### Bukkit/Paper
-
-Before:
-
-```java
-Platform platform = new BukkitPlatformProvider(this);
-ConfigManager configManager = new ConfigManager(platform);
-Logger logger = new Logger(platform, this, configManager);
-LanguageManager languageManager = new LanguageManager(this, configManager);
-languageManager.init("en");
-languageManager.addMagicUtilsMessages();
-logger.setLanguageManager(languageManager);
-Messages.register(getName(), languageManager);
-```
-
-After:
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this)
- .buildRuntime();
-```
-
-What you gain:
-
-- one managed shutdown handle
-- consistent logger/lang/messages wiring
-- optional command registry integration
-- easy access through `MagicRuntime`
-
-### Fabric
-
-Before:
-
-```java
-Platform platform = new FabricPlatformProvider(server);
-ConfigManager configManager = new ConfigManager(platform);
-Logger logger = new Logger(platform, configManager, "MyMod");
-```
-
-After:
-
-```java
-FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server)
- .buildRuntime();
-```
-
-Important: Fabric command registration still happens inside
-`CommandRegistrationCallback.EVENT`.
-
-### Velocity
-
-Before:
-
-```java
-Platform platform = new VelocityPlatformProvider(proxy, slf4j, dataDirectory, this);
-ConfigManager configManager = new ConfigManager(platform);
-LoggerCore logger = new LoggerCore(platform, configManager, this, "MyPlugin");
-```
-
-After:
-
-```java
-VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory)
- .slf4j(slf4j)
- .buildRuntime();
-```
-
-## 2. `initialize(...)` -> Explicit Registry Or Bootstrap
-
-Older code often used the static default registry:
-
-```java
-CommandRegistry.initialize(plugin, "myplugin", logger);
-CommandRegistry.register(plugin, new DonateCommand());
-```
-
-Preferred now:
-
-```java
-CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger);
-registry.registerCommand(new DonateCommand());
-```
-
-Or through bootstrap:
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)
- .permissionPrefix("myplugin")
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new DonateCommand()))
- .buildRuntime();
-```
-
-Why prefer this:
-
-- easier testing
-- fewer hidden globals
-- clearer ownership in multi-plugin or multi-mod setups
-
-## 3. `CommandSpec.builder(...)` -> `MagicCommand.builder(...)`
-
-Older builder-based command code often produced a detached spec:
-
-```java
-CommandSpec spec = CommandSpec.builder("donate")
- .execute(ctx -> CommandResult.success("ok"))
- .build();
-
-registry.registerSpec(spec);
-```
-
-Preferred now:
-
-```java
-MagicCommand command = MagicCommand.builder("donate")
- .execute(ctx -> CommandResult.success("ok"))
- .build();
-
-registry.registerCommand(command);
-```
-
-This matters because builder-authored commands are now real `MagicCommand`
-instances. They can use the same runtime adaptation API as annotation-based
-commands:
-
-- `withName(...)`
-- `addAlias(...)`
-- `addSubCommand(...)`
-- `setExecute(...)`
-- `mount(existingCommand)`
-
-`registerSpec(...)` still works for migration, but it is now the compatibility
-path rather than the primary one.
-
-## 4. Ad-Hoc Reload Logic -> `MagicRuntime` Bindings
-
-Older reload code often looked like this:
-
-```java
-if (client != null) {
- client.close();
-}
-client = MagicHttpClient.builder(platform, configManager)
- .baseUrl(config.monitoring.baseUrl)
- .build();
-```
-
-Preferred now:
-
-```java
-MagicRuntimeConfigBinding binding = runtime.bindConfig(
- "http.monitoring",
- ServiceConfig.class,
- config -> MagicHttpClient.builder(runtime.platform(), runtime.configManager())
- .baseUrl(config.monitoring.baseUrl)
- .build(),
- "monitoring"
-);
-```
-
-Or use the higher-level profile wrapper:
-
-```java
-MagicHttpClientProfile monitoring = MagicHttpClientProfile
- .builder(runtime, "http.monitoring", ServiceConfig.class)
- .sections("monitoring")
- .baseUrl(config -> config.monitoring.baseUrl)
- .build();
-```
-
-## 4. Manual Shutdown -> One Runtime Close
-
-Before:
-
-```java
-configManager.shutdown();
-CommandRegistry.shutdown(plugin);
-Messages.unregister(getName());
-```
-
-After:
-
-```java
-magic.runtime().close();
-```
-
-That only works when the services were created through bootstrap or registered
-inside the runtime.
-
-## 5. When Not To Migrate
-
-Keep the manual style when:
-
-- you are integrating MagicUtils into an unusual platform
-- you need partial module wiring without the bootstrap defaults
-- you intentionally manage service lifecycles yourself
-
-Even in those cases, `MagicRuntime` is still useful as a local lifecycle
-container.
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md
deleted file mode 100644
index 4f577b0cc..000000000
--- a/docs/getting-started/quickstart.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# Quickstart
-
-The recommended path is bootstrap-first: use the platform bootstrap helper when
-it exists, keep the returned runtime handle, and close it when the platform
-shuts down.
-
-If most of your logic lives directly in `magicutils-core`, keep the platform
-entrypoint thin and move the shared services behind `MagicRuntime`.
-
-That does not mean `common` should call `forPlugin(...)` or `forMod(...)`.
-Those bootstrap helpers stay in the platform module, which then hands the
-runtime into shared code.
-
-## Bukkit/Paper
-
-```java
-public final class MyPlugin extends JavaPlugin {
- private BukkitBootstrap.RuntimeResult magic;
-
- @Override
- public void onEnable() {
- magic = BukkitBootstrap.forPlugin(this)
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new ExampleCommand()))
- .buildRuntime();
-
- magic.logger().info("Ready.");
- }
-
- @Override
- public void onDisable() {
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- }
-}
-```
-
-`BukkitBootstrap` wires `Platform`, `ConfigManager`, `Logger`,
-`LanguageManager`, `Messages`, and an optional `CommandRegistry`.
-
-## Fabric
-
-```java
-public final class MyMod implements ModInitializer {
- private static final String MOD_ID = "mymod";
-
- private MinecraftServer server;
- private FabricBootstrap.RuntimeResult magic;
-
- @Override
- public void onInitialize() {
- magic = FabricBootstrap.forMod(MOD_ID, () -> server)
- .enableCommands()
- .buildRuntime();
-
- CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
- if (magic.commandRegistry() != null) {
- magic.commandRegistry().registerCommand(dispatcher, new ExampleCommand());
- }
- });
-
- ServerLifecycleEvents.SERVER_STARTED.register(server -> this.server = server);
- ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
- this.server = null;
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- });
- }
-}
-```
-
-`FabricBootstrap` sets up the shared services early, while actual command
-registration still happens inside Fabric's Brigadier callback.
-
-## Velocity
-
-```java
-@Plugin(id = "myplugin", name = "MyPlugin", version = "1.0.0")
-public final class MyPlugin {
- private final ProxyServer proxy;
- private final org.slf4j.Logger slf4j;
- private final Path dataDirectory;
- private VelocityBootstrap.RuntimeResult magic;
-
- @Inject
- public MyPlugin(ProxyServer proxy,
- org.slf4j.Logger slf4j,
- @DataDirectory Path dataDirectory) {
- this.proxy = proxy;
- this.slf4j = slf4j;
- this.dataDirectory = dataDirectory;
- }
-
- @Subscribe
- public void onProxyInitialize(ProxyInitializeEvent event) {
- magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory)
- .slf4j(slf4j)
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new ExampleCommand()))
- .buildRuntime();
-
- magic.logger().info("Ready.");
- }
-
- @Subscribe
- public void onProxyShutdown(ProxyShutdownEvent event) {
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- }
-}
-```
-
-`VelocityBootstrap` creates a managed `LoggerCore`, registers shutdown cleanup,
-and can wire the Velocity command registry for you.
-
-## NeoForge
-
-NeoForge currently uses the manual wiring path.
-
-```java
-public final class MyMod {
- private static final String MOD_ID = "mymod";
-
- private final Platform platform;
- private final ConfigManager configManager;
- private final LoggerCore logger;
- private final CommandRegistry commands;
-
- public MyMod() {
- platform = new NeoForgePlatformProvider();
- configManager = new ConfigManager(platform);
- logger = new LoggerCore(platform, configManager, this, "MyMod");
- commands = CommandRegistry.create(MOD_ID, MOD_ID, logger);
-
- logger.info("Ready.");
- }
-
- @SubscribeEvent
- public void onRegisterCommands(RegisterCommandsEvent event) {
- commands.registerCommand(event.getDispatcher(), new ExampleCommand());
- }
-}
-```
-
-## Using The Runtime Handle
-
-Every `buildRuntime()` call returns `MagicRuntime`, which exposes the shared
-services as typed components:
-
-```java
-MagicRuntime runtime = magic.runtime();
-ConfigManager configManager = runtime.requireComponent(ConfigManager.class);
-LoggerCore logger = runtime.requireComponent(LoggerCore.class);
-LanguageManager languages = runtime.findComponent(LanguageManager.class).orElse(null);
-```
-
-Use named resources and config bindings when you want runtime-managed clients or
-other reloadable services.
-
-## Core / Common Logic
-
-If your plugin or mod is mostly shared logic plus a thin platform bootstrap,
-keep the common layer built around `MagicRuntime` and platform-agnostic
-services.
-
-See [Core / Common Logic](../platforms/core.md) for the recommended split
-between platform glue and shared code.
-
-## Next Steps
-
-- Pick the modules you need from the Modules section.
-- Use the platform pages for more detailed bootstrap notes.
-- Use the Core / Common Logic page for shared/common module structure.
-- Read the Runtime guide for `MagicRuntime`, managed resources, and config bindings.
-- Use the Migration guide if you are moving from older manual wiring examples.
-- See the HTTP client page for `MagicRuntime`-bound profiles.
diff --git a/docs/getting-started/runtime.md b/docs/getting-started/runtime.md
deleted file mode 100644
index 439ca782b..000000000
--- a/docs/getting-started/runtime.md
+++ /dev/null
@@ -1,178 +0,0 @@
-# Runtime
-
-`MagicRuntime` is the managed service container behind the bootstrap-first
-setup. It gives you one place to access core services, register extra
-components, manage closeable resources, and rebuild config-backed clients on
-reload.
-
-## Getting A Runtime
-
-The recommended path is `buildRuntime()`:
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this)
- .enableCommands()
- .buildRuntime();
-
-MagicRuntime runtime = magic.runtime();
-```
-
-The same pattern exists for:
-
-- `BukkitBootstrap`
-- `FabricBootstrap`
-- `VelocityBootstrap`
-
-NeoForge and custom platforms can build `MagicRuntime` manually.
-
-## Core Components
-
-Every runtime starts with typed components for the core services:
-
-- `Platform`
-- `ConfigManager`
-- `LoggerCore`
-- `LanguageManager` when configured
-
-Access them via the typed component registry:
-
-```java
-Platform platform = runtime.requireComponent(Platform.class);
-ConfigManager configManager = runtime.requireComponent(ConfigManager.class);
-LoggerCore logger = runtime.requireComponent(LoggerCore.class);
-CommandRegistry commands = runtime.findComponent(CommandRegistry.class).orElse(null);
-```
-
-Use `findComponent(...)` when the component is optional and
-`requireComponent(...)` when its absence is a bug.
-
-## Typed Components
-
-Register and replace typed components at runtime:
-
-```java
-runtime.putComponent(MyService.class, new MyService());
-
-MyService service = runtime.requireComponent(MyService.class);
-Optional opt = runtime.findComponent(MyService.class);
-```
-
-`findComponent(...)` also matches assignable types, so requesting an interface
-will find a registered implementation.
-
-## Named Components
-
-`MagicRuntime` also exposes a named registry for dynamic resources:
-
-```java
-runtime.putNamedComponent("service.cache", cacheClient);
-
-CacheClient cache = runtime.requireNamedComponent("service.cache", CacheClient.class);
-Optional opt = runtime.findNamedComponent("service.cache", CacheClient.class);
-```
-
-Remove a named component when it is no longer needed:
-
-```java
-runtime.removeNamedComponent("service.cache");
-```
-
-Named components are especially useful for:
-
-- reloadable clients
-- plugin-owned service singletons
-- resources keyed by logical role (`http.monitoring`, `ws.gateway`)
-
-## Managed Resources
-
-Use `resource(...)` when you want a stable named slot that closes replaced
-resources automatically:
-
-```java
-MagicRuntimeResource monitoring = runtime.resource(
- "http.monitoring",
- MagicHttpClient.builder(runtime.platform(), runtime.configManager())
- .baseUrl("https://api.example.com/")
- .build()
-);
-
-MagicHttpClient client = monitoring.require();
-monitoring.set(MagicHttpClient.builder(runtime.platform(), runtime.configManager())
- .baseUrl("https://api-two.example.com/")
- .build());
-```
-
-The resource is also exposed through the named component registry under the same
-name.
-
-## Config Bindings
-
-Use `bindConfig(...)` when a closeable resource should rebuild automatically on
-matching config reloads:
-
-```java
-MagicRuntimeConfigBinding binding = runtime.bindConfig(
- "http.monitoring",
- ServiceConfig.class,
- config -> MagicHttpClient.builder(runtime.platform(), runtime.configManager())
- .baseUrl(config.monitoring.baseUrl)
- .build(),
- "monitoring"
-);
-
-MagicHttpClient client = binding.require();
-```
-
-This pattern works well for:
-
-- HTTP clients
-- WebSocket clients
-- database pools
-- SDK clients
-
-The HTTP client module also provides higher-level wrappers:
-
-- `MagicHttpClientProfile`
-- `MagicWebSocketClientProfile`
-
-Use those when the resource being managed is specifically an HTTP or WebSocket
-client.
-
-## Lifecycle
-
-`MagicRuntime.close()`:
-
-- unregisters its platform shutdown hook when one was installed
-- closes managed resources in reverse registration order
-- closes runtime resources and config bindings
-- can shut down `ConfigManager` automatically
-
-Bootstrap helpers already configure the runtime so that `magic.runtime().close()`
-is the one shutdown call you usually need in your plugin or mod.
-
-## Building A Runtime Manually
-
-For NeoForge or custom platforms, build it directly:
-
-```java
-MagicRuntime runtime = MagicRuntime.builder(platform, configManager, logger)
- .languageManager(languageManager)
- .component(MyPlugin.class, this)
- .manage("database", databaseClient)
- .onClose("metrics", metrics::flush)
- .manageConfigManager(true)
- .autoRegisterShutdown(true)
- .build();
-```
-
-Builder controls:
-
-- `languageManager(...)`
-- `component(...)`
-- `manage(...)`
-- `onClose(...)`
-- `manageConfigManager(...)`
-- `autoRegisterShutdown(...)`
-
-Disable `autoRegisterShutdown(...)` when the platform already has an explicit
-shutdown phase you want to own manually.
diff --git a/docs/getting-started/versioning.md b/docs/getting-started/versioning.md
deleted file mode 100644
index 08226f4ef..000000000
--- a/docs/getting-started/versioning.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Versioning
-
-This site is versioned with `mike` and published to GitHub Pages. Each
-documentation version matches a MagicUtils release tag.
-
-## Documentation versions
-
-The version selector in the header switches between:
-
-- `stable`: the latest tagged release.
-- `dev`: the current development branch.
-
-Examples in the docs use `{{ magicutils_version }}` which is automatically
-set to the active docs version.
-
-## Tag releases (for maintainers)
-
-Use the release helper to sync `gradle.properties`, push the release branch,
-and dispatch the tag workflow safely:
-
-```bash
-python3 scripts/publish_release.py 1.10.0 --dry-run
-python3 scripts/publish_release.py 1.10.0
-```
-
-The helper waits until `origin/][` resolves to the pushed local HEAD before
-dispatching `release.yml`, which avoids tagging an older commit when GitHub has
-not caught up with the branch update yet.
-
-When a release tag is created (for example `v1.10.0`), the workflow deploys a
-new docs version and updates the `stable` alias.
-
-```
-# Local
-mike deploy --update-aliases 1.10.0 stable
-mike set-default stable
-```
-
-## Development docs
-
-For development builds, the workflow deploys `dev` and sets `latest` as the
-default alias.
-
-```
-# Local
-mike deploy --update-aliases dev latest
-mike set-default latest
-```
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index a97e582d1..000000000
--- a/docs/index.md
+++ /dev/null
@@ -1,125 +0,0 @@
-# MagicUtils
-
-MagicUtils is a modular toolkit for Bukkit/Paper, Fabric, Velocity, and
-NeoForge. It provides shared building blocks for configuration, localisation,
-commands, logging, placeholders, HTTP clients, and platform adapters.
-
-The same stack also works well in multi-module projects where platform adapters
-only bootstrap `MagicRuntime` and the real feature logic lives in shared
-`common` modules.
-
-## Highlights
-
-- Bootstrap-first setup for Bukkit, Fabric, and Velocity via
- `BukkitBootstrap`, `FabricBootstrap`, and `VelocityBootstrap`.
-- `MagicRuntime` container for managed shutdown hooks, typed components, and
- named runtime resources.
-- Config manager with JSON/JSONC, YAML, and TOML support plus migrations.
-- Annotation-first command framework with type parsers, options, and Brigadier
- support where the platform supports it.
-- Adventure-based logger with rich formatting, sub-loggers, and help styling.
-- Language manager with MiniMessage, bundled messages, and per-player
- overrides.
-- HTTP client wrapper with JSON mapping, retries, multipart uploads, and
- runtime-bound profiles.
-- WebSocket client with the same builder pattern, config integration, and
- runtime profiles.
-- Platform-agnostic player lifecycle and message events.
-- Config validation annotations (`@MinValue`, `@MaxValue`, `@ConfigSerializable`).
-- Placeholder registry with Bukkit PlaceholderAPI and Fabric placeholder
- bridges.
-
-## Modules At A Glance
-
-| Layer | Artifacts | Notes |
-| --- | --- | --- |
-| Platform API | `magicutils-api` | `Platform`, `Audience`, `TaskScheduler`, and shared interfaces. |
-| Core stack | `magicutils-core` | Shared runtime container plus core config/lang/logger/placeholder wiring. |
-| Feature modules | `magicutils-logger`, `magicutils-commands`, `magicutils-config`, `magicutils-lang`, `magicutils-placeholders`, `magicutils-http-client` | Mix and match for manual setups. |
-| Format helpers | `magicutils-config-yaml`, `magicutils-config-toml` | Enable extra config formats. |
-| Platform adapters | `magicutils-bukkit`, `magicutils-fabric`, `magicutils-velocity`, `magicutils-neoforge` | Wire MagicUtils to each runtime. |
-| Platform bundles | `magicutils-bukkit-bundle`, `magicutils-fabric-bundle` | Shared server-side installs for Bukkit/Paper and Fabric. |
-| Fabric integrations | `magicutils-commands-fabric`, `magicutils-logger-fabric`, `magicutils-placeholders-fabric` | Fabric-specific command, logger, and placeholder layers. |
-| Brigadier integrations | `magicutils-commands-brigadier`, `magicutils-commands-neoforge` | Shared Brigadier base and NeoForge command wiring. |
-
-## Quick Start
-
-1. Add the GitHub Pages Maven repository.
-2. Add one platform entry point.
-3. Wire the runtime through the recommended bootstrap helper.
-
-```kotlin
-repositories {
- maven("https://maven.theroer.dev/releases/")
-}
-```
-
-=== "Bukkit/Paper"
- ```kotlin
- dependencies {
- implementation("dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}")
- }
- ```
-
- ```java
- BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this)
- .enableCommands()
- .buildRuntime();
- ```
-
-=== "Fabric"
- ```kotlin
- dependencies {
- modImplementation(include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}"))
- modCompileOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev")
- modRuntimeOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev")
- }
- ```
-
- ```java
- FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server)
- .enableCommands()
- .buildRuntime();
- ```
-
-=== "Velocity"
- ```kotlin
- dependencies {
- implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}")
- }
- ```
-
- ```java
- VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory)
- .enableCommands()
- .buildRuntime();
- ```
-
-=== "NeoForge"
- ```kotlin
- dependencies {
- implementation("dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}")
- implementation("dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}")
- }
- ```
-
- ```java
- Platform platform = new NeoForgePlatformProvider();
- ConfigManager configManager = new ConfigManager(platform);
- LoggerCore logger = new LoggerCore(platform, configManager, this, "MyMod");
- CommandRegistry commands = CommandRegistry.create("mymod", "mymod", logger);
- ```
-
-`buildRuntime()` returns a managed `MagicRuntime` wrapper, so you can keep one
-runtime handle and close it cleanly on shutdown.
-
-## Where To Go Next
-
-- Read the Core / Common Logic page when your code lives mostly in
- `magicutils-core`.
-- Read the installation guide for bundle options and modular setups.
-- Jump to Quickstart for end-to-end bootstrap examples per platform.
-- Read the Runtime guide for `MagicRuntime` patterns and lifecycle management.
-- Use the Migration guide when updating older code samples or plugins.
-- Use the module pages for deeper API examples and config details.
-- Use the version selector in the header to switch between releases.
diff --git a/docs/modules/commands-cheatsheet.md b/docs/modules/commands-cheatsheet.md
deleted file mode 100644
index 60d455673..000000000
--- a/docs/modules/commands-cheatsheet.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# Commands Cheat Sheet
-
-Quick reference for the MagicUtils command system.
-
-## Minimal Registry Setup
-
-```java
-CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger);
-registry.registerCommand(new DonateCommand());
-```
-
-## Minimal Annotated Command
-
-```java
-@CommandInfo(name = "donate", description = "Main command")
-public final class DonateCommand extends MagicCommand {
- public CommandResult execute(@Sender MagicSender sender) {
- return CommandResult.success("ok");
- }
-}
-```
-
-## Threading (Async)
-
-```java
-@CommandInfo(name = "donate", threading = CommandThreading.ASYNC)
-public final class DonateCommand extends MagicCommand {
- @SubCommand(name = "give", threading = CommandThreading.ASYNC)
- public CommandResult give(@Sender MagicSender sender, Player target) { ... }
-}
-```
-
-## Subcommands And Nested Paths
-
-```java
-@SubCommand(name = "give")
-public CommandResult give(@Sender MagicSender sender, Player target) { ... }
-
-@SubCommand(path = {"npc", "commands"}, name = "add")
-public CommandResult addNpcCommand(...) { ... }
-```
-
-## Options / Flags
-
-```java
-public CommandResult give(
- @Option(shortNames = {"a"}, longNames = {"amount"}) int amount,
- @Option(shortNames = {"s"}, longNames = {"silent"}, flag = true) boolean silent
-) { ... }
-```
-
-Accepted forms:
-
-- `--amount 5`
-- `-a 5`
-- `--silent` / `-s`
-
-## Optional + Default Values
-
-```java
-public CommandResult set(
- @DefaultValue("en") String lang,
- @OptionalArgument Player target
-) { ... }
-```
-
-## Greedy Text
-
-```java
-public CommandResult say(@Greedy String message) { ... }
-```
-
-## Suggestions
-
-```java
-@Suggest("@players") Player target
-@Suggest("{easy,hard}") String mode
-@Suggest("getItems") String item
-```
-
-Suggestion methods can be:
-
-- `List getItems()`
-- `List getItems(Player player)`
-- `List getItems(ServerCommandSource sender)`
-- `List getItems(CommandSource sender)`
-
-## Sender Injection
-
-```java
-public CommandResult execute(@Sender MagicSender sender) { ... }
-```
-
-Allowed senders:
-
-`ANY`, `PLAYER`, `CONSOLE`, `BLOCK`, `MINECART`, `PROXIED`, `REMOTE`.
-
-## Builder API Snippet
-
-```java
-MagicCommand command = MagicCommand.builder("donate")
- .description("Main command")
- .aliases("d")
- .threading(CommandThreading.ASYNC)
- .execute(ctx -> CommandResult.success("ok"))
- .subCommand(SubCommandSpec.builder("give")
- .argument(CommandArgument.builder("player", Player.class).build())
- .threading(CommandThreading.ASYNC)
- .execute(ctx -> CommandResult.success("ok"))
- .build())
- .build();
-
-registry.registerCommand(command);
-```
-
-## Mount Existing Command Tree
-
-```java
-MagicCommand admin = MagicCommand.builder("admin")
- .mount("punish", new BanCommand())
- .build();
-```
diff --git a/docs/modules/commands.md b/docs/modules/commands.md
deleted file mode 100644
index 38cb365ec..000000000
--- a/docs/modules/commands.md
+++ /dev/null
@@ -1,424 +0,0 @@
-# Commands
-
-MagicUtils commands are annotation-first, but the runtime model is registry
-based. Each platform exposes a `CommandRegistry` that owns parsers,
-permissions, and command registration for that plugin or mod.
-
-See [Commands Cheat Sheet](commands-cheatsheet.md) for a quick reference and
-[Permissions](permissions.md) for node generation details.
-
-## Registration Models
-
-There are three common ways to obtain a registry:
-
-1. Bootstrap helper creates it for you.
-2. `CommandRegistry.create(...)` returns an instance you keep explicitly.
-3. Legacy `CommandRegistry.initialize(...)` / `createDefault(...)` creates the
- default registry for the current platform.
-
-For multi-plugin or multi-mod setups, prefer an explicit registry instance or
-the scoped static overloads. The no-arg `register(...)` methods operate on the
-default registry.
-
-## Platform Registration
-
-### Bukkit/Paper
-
-Bootstrap-first:
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)
- .permissionPrefix("myplugin")
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new DonateCommand()))
- .buildRuntime();
-```
-
-Manual registry:
-
-```java
-CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger);
-registry.registerCommand(new DonateCommand());
-registry.registerCommand(new AdminCommand());
-```
-
-### Fabric
-
-Bootstrap-first:
-
-```java
-FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server)
- .permissionPrefix("mymod")
- .enableCommands()
- .buildRuntime();
-
-CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
- if (magic.commandRegistry() != null) {
- magic.commandRegistry().registerCommand(dispatcher, new DonateCommand());
- }
-});
-```
-
-Manual registry:
-
-```java
-CommandRegistry registry = CommandRegistry.create("mymod", "mymod", logger, 2);
-
-CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
- registry.registerCommand(dispatcher, new DonateCommand());
-});
-```
-
-### Velocity
-
-Bootstrap-first:
-
-```java
-VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, "MyPlugin", dataDirectory)
- .permissionPrefix("myplugin")
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new DonateCommand()))
- .buildRuntime();
-```
-
-Manual registry:
-
-```java
-CommandRegistry registry = CommandRegistry.create(proxy, plugin, "myplugin", loggerCore);
-registry.registerCommand(new DonateCommand());
-```
-
-### NeoForge
-
-NeoForge currently uses the manual path:
-
-```java
-CommandRegistry registry = CommandRegistry.create("mymod", "mymod", loggerCore, 2);
-
-@SubscribeEvent
-public void onRegisterCommands(RegisterCommandsEvent event) {
- registry.registerCommand(event.getDispatcher(), new DonateCommand());
-}
-```
-
-The second argument is always the permission prefix used when generating nodes.
-
-## Annotation-Based Commands
-
-Use `@CommandInfo` on the class and `@SubCommand` on methods. A method named
-`execute` without `@SubCommand` is treated as the root handler.
-
-```java
-@CommandInfo(
- name = "donate",
- description = "DonateMenu main command",
- aliases = {"d"},
- permission = "donate.use"
-)
-public final class DonateCommand extends MagicCommand {
-
- public CommandResult execute(@Sender MagicSender sender) {
- return CommandResult.success("Opened menu");
- }
-
- @SubCommand(name = "give", description = "Give currency to a player")
- public CommandResult give(
- @Sender MagicSender sender,
- @ParamName("player") Player target,
- @Option(shortNames = {"a"}, longNames = {"amount"}) int amount,
- @Option(shortNames = {"s"}, longNames = {"silent"}, flag = true) boolean silent
- ) {
- return CommandResult.success(silent ? "" : "Done");
- }
-}
-```
-
-Nested subcommands are supported via `path`:
-
-```java
-@SubCommand(path = {"npc", "commands"}, name = "add")
-public CommandResult addNpcCommand(...) { ... }
-```
-
-### Common Annotations
-
-- `@ParamName` overrides argument names for help output.
-- `@OptionalArgument` or `@DefaultValue("...")` marks a parameter optional.
-- `@Greedy` captures the rest of the input.
-- `@Suggest("source")` adds completion hints.
-- `@Sender` injects the sender and hides it from help output.
-- `@Option(shortNames = {"a"}, longNames = {"amount"})` enables `-a 5` and
- `--amount 5`. Set `flag = true` for toggles such as `-s` / `--silent`.
-
-`@Sender` supports sender filtering via `AllowedSender`:
-
-- `ANY`, `PLAYER`, `CONSOLE`
-- `BLOCK`, `MINECART`, `PROXIED`, `REMOTE`
-
-Platform-specific sender types can also be injected directly:
-
-- Bukkit: `CommandSender`, `Player`
-- Fabric: `ServerCommandSource`, `ServerPlayerEntity`
-- Velocity: `CommandSource`, `Player`, `ConsoleCommandSource`
-- NeoForge: `CommandSourceStack`, `ServerPlayer`
-
-## Suggestions And Type Parsers
-
-Suggestions can come from:
-
-- Built-in type parsers (players, worlds, enums, booleans).
-- Special sources such as `@players`, `@worlds`, `@commands`.
-- Inline lists: `@Suggest("{on,off,reset}")`.
-- Methods on the command class: `@Suggest("getItems")`.
-
-Suggestion methods can be:
-
-- `String[] getItems()` or `List getItems()`
-- `getItems(Player player)`
-- `getItems(ServerCommandSource sender)`
-- `getItems(CommandSource sender)`
-
-Built-in sources:
-
-- `@players`, `@player`, `@allplayers`
-- `@offlineplayers` (Bukkit only)
-- `@worlds`, `@world`
-- `@language_keys` (Bukkit only)
-- `@commands`
-- `{a,b,c}` inline list syntax
-
-Custom parsers are registered on the registry's parser registry:
-
-```java
-registry.commandManager()
- .getTypeParserRegistry()
- .register(new MyTypeParser());
-```
-
-## Permissions
-
-Permissions can be defined at three levels:
-
-- `@CommandInfo.permission`
-- `@SubCommand.permission`
-- `@Permission` on parameters
-
-Generated nodes use this shape when you do not provide explicit values:
-
-- Command: `commands.`
-- Subcommand: `commands..subcommand.`
-- Argument: `commands..subcommand..argument.`
-
-These nodes are prefixed by the registry permission prefix. See
-[Permissions](permissions.md) for the platform-specific behaviour.
-
-`MagicPermissionDefault` controls the default access policy:
-
-- `TRUE`
-- `OP`
-- `NOT_OP`
-- `FALSE`
-
-For manual checks outside annotation processing, use `MagicSender`:
-
-```java
-MagicSender sender = MagicSender.wrap(rawSender);
-if (MagicSender.hasPermission(rawSender, "myplugin.admin")) {
- // adapter-default fallback
-}
-if (sender != null && sender.hasPermission("myplugin.admin", 4)) {
- // explicit fallback override for this check
-}
-```
-
-The overload with `fallbackOpLevel` is mainly useful on Fabric and NeoForge,
-where adapters may fall back to command-source permission levels.
-
-## CommandResult
-
-`CommandResult.success()` and `CommandResult.failure()` control whether MagicUtils
-sends feedback automatically.
-
-- `CommandResult.success()` means success with no reply text.
-- `CommandResult.success("Done")` sends a success reply.
-- `CommandResult.failure("No permission")` sends a failure reply.
-
-## Threading
-
-Commands run on the main thread by default. Use `CommandThreading.ASYNC` for
-IO-heavy or CPU-heavy work:
-
-```java
-@CommandInfo(name = "donate", threading = CommandThreading.ASYNC)
-public final class DonateCommand extends MagicCommand {
- public CommandResult execute(@Sender MagicSender sender) {
- return CommandResult.success("done");
- }
-
- @SubCommand(name = "give", threading = CommandThreading.ASYNC)
- public CommandResult give(@Sender MagicSender sender, Player target) {
- return CommandResult.success("ok");
- }
-}
-```
-
-Builder equivalents:
-
-```java
-MagicCommand.builder("donate")
- .threading(CommandThreading.ASYNC)
- .execute(ctx -> CommandResult.success("done"))
- .build();
-
-SubCommandSpec.builder("give")
- .threading(CommandThreading.ASYNC)
- .execute(ctx -> CommandResult.success("ok"))
- .build();
-```
-
-Only mark commands as async when your code is thread-safe. When you need to
-touch platform APIs again, switch back to the main thread via
-`Platform.runOnMain(...)` or `Tasks.runOnMain(...)`.
-
-## Help Output
-
-The help renderer respects permissions and hides commands or arguments the
-sender cannot access. It is styled through `logger.{ext}` under the `help`
-section.
-
-### Standalone Help Command
-
-Bukkit and Fabric ship a ready-to-register `HelpCommand` wrapper:
-
-```java
-registry.registerCommand(new HelpCommand(logger, registry));
-```
-
-You can rename it at runtime:
-
-```java
-registry.registerCommand(new HelpCommand(logger, registry)
- .withName("donatehelp")
- .addAlias("dhelp"));
-```
-
-### Help As A Subcommand
-
-Use `HelpCommandSupport` when you want help inside another command tree or on
-platforms that do not ship a dedicated wrapper:
-
-```java
-registry.registerCommand(new DonateCommand()
- .addSubCommand(HelpCommandSupport.createHelpSubCommand(
- "help",
- loggerCore,
- registry::commandManager
- )));
-```
-
-## MagicSender
-
-`MagicSender` is the platform-neutral sender wrapper used throughout the
-command system:
-
-```java
-MagicSender sender = MagicSender.wrap(rawSender);
-if (MagicSender.hasPermission(rawSender, "my.permission")) {
- // ...
-}
-```
-
-Use it when you want shared command logic across Bukkit, Fabric, Velocity, and
-NeoForge without branching on raw sender types.
-
-## Builder API
-
-Use the builder API when you need runtime composition but still want a real
-`MagicCommand` instance:
-
-```java
-MagicCommand donateCommand = MagicCommand.builder("donate")
- .description("DonateMenu main command")
- .aliases("d")
- .execute(ctx -> CommandResult.success("Opened menu"))
- .subCommand(SubCommandSpec.builder("give")
- .description("Give currency")
- .argument(CommandArgument.builder("player", Player.class).build())
- .argument(CommandArgument.builder("amount", Integer.class).build())
- .execute(ctx -> CommandResult.success("ok"))
- .build())
- .build();
-
-registry.registerCommand(donateCommand);
-```
-
-You can mix annotations with runtime overrides:
-
-- `withName(...)`, `addAlias(...)`, `removeAlias(...)`
-- `addSubCommand(SubCommandSpec>)`
-- `setExecute(...)`
-- `mount(MagicCommand)` / `mount("route", existingCommand)`
-
-Nested builder subcommands are supported as well:
-
-```java
-SubCommandSpec npcAdd = SubCommandSpec.builder("add")
- .path("npc", "commands")
- .description("Add NPC command")
- .execute(ctx -> CommandResult.success("ok"))
- .build();
-```
-
-## Composing Existing Commands
-
-Already-authored annotation commands can be mounted under another command tree
-without rewriting them into `SubCommandSpec` form:
-
-```java
-MagicCommand adminCommand = MagicCommand.builder("admin")
- .mount("punish", new BanCommand())
- .build();
-
-registry.registerCommand(adminCommand);
-```
-
-This is intentionally different from `withName("punish")`:
-
-- `withName(...)` mutates the authored command instance itself
-- `mount("punish", command)` keeps the source command identity intact and only
- changes the route segment inside the parent tree
-
-Mounting snapshots the child command structure at mount time. That means:
-
-- changing the child name or aliases later does not rewrite the already-mounted
- parent tree
-- the mounted command still executes against the original child instance
-- when you override the mounted route, root aliases from the child are not
- exposed automatically
-
-## Mutation Lifecycle
-
-`MagicCommand` is mutable only during definition and composition:
-
-```java
-MagicCommand command = MagicCommand.builder("donate")
- .execute(ctx -> CommandResult.success("ok"))
- .build()
- .addAlias("d");
-```
-
-After `registry.registerCommand(...)` or `commandManager.register(...)`, the
-command is frozen. Later calls to:
-
-- `withName(...)`
-- `addAlias(...)`
-- `removeAlias(...)`
-- `addSubCommand(...)`
-- `setExecute(...)`
-- `mount(...)`
-
-throw `IllegalStateException`. If you still use `registerSpec(...)`, it remains
-supported as a compatibility path and is internally converted into the same
-`MagicCommand` runtime model.
-
-For runtime diagnostics mounted the same way, see [Diagnostics](diagnostics.md).
diff --git a/docs/modules/config-advanced.md b/docs/modules/config-advanced.md
deleted file mode 100644
index a38459152..000000000
--- a/docs/modules/config-advanced.md
+++ /dev/null
@@ -1,157 +0,0 @@
-# Config Advanced
-
-This page covers format selection, migrations, adapters, and runtime-aware
-reload patterns.
-
-## Format Selection
-
-If your `@ConfigFile` uses `{ext}`, MagicUtils can switch formats using:
-
-- `.format` next to the config file
-- `magicutils.format` in the config root directory
-- `-Dmagicutils.config.format=...`
-- `MAGICUTILS_CONFIG_FORMAT`
-
-Supported values include `json`, `jsonc`, `yml`, `yaml`, and `toml` depending
-on the installed format helpers.
-
-If multiple candidate files exist, MagicUtils picks one and logs a warning.
-
-## Format Migration
-
-When the selected format changes and an older format file exists, MagicUtils
-can migrate the data into the new target file. This is useful when moving from
-JSONC to YAML or TOML without forcing users to recreate their configs.
-
-## Schema Migrations
-
-Register ordered `ConfigMigration` steps to evolve config schemas:
-
-```java
-manager.registerMigrations(MyConfig.class,
- new ConfigMigration() {
- public String fromVersion() { return "0"; }
- public String toVersion() { return "1"; }
- public void migrate(Map root) {
- root.put("enabled", true);
- }
- }
-);
-```
-
-MagicUtils stores the current schema version in `config-version`.
-
-## Custom Adapters
-
-Use `ConfigAdapters.register(...)` for custom value types:
-
-```java
-ConfigAdapters.register(Duration.class, new ConfigValueAdapter<>() {
- public Duration deserialize(Object value) { ... }
- public Object serialize(Duration value) { ... }
-});
-```
-
-Register adapters before the affected config class is first loaded.
-
-## Change Subscriptions
-
-Use `subscribeChanges(...)` when you want an unsubscribe handle:
-
-```java
-ListenerSubscription subscription = manager.subscribeChanges(MyConfig.class, (cfg, sections) -> {
- // apply live update
-});
-```
-
-Use `onChange(...)` when you only need fire-and-forget registration.
-
-## Runtime Resource Binding
-
-`MagicRuntime` can rebuild managed resources on matching config changes:
-
-```java
-MagicRuntimeConfigBinding binding = runtime.bindConfig(
- "service.client",
- ServiceConfig.class,
- config -> new ReloadableClient(config), // ReloadableClient implements AutoCloseable
- "service"
-);
-```
-
-Useful properties of this pattern:
-
-- the latest resource is accessible via `binding.require()`
-- the same resource is exposed through
- `runtime.requireNamedComponent("service.client", ReloadableClient.class)`
-- replaced resources are closed automatically
-- `binding.close()` removes the named runtime component and stops listening for
- config changes
-
-## Validation And Constraints
-
-### `@MinValue` / `@MaxValue`
-
-Clamp numeric config values to a safe range on load:
-
-```java
-@ConfigValue("interval_seconds")
-@MinValue(5)
-@MaxValue(3600)
-private int intervalSeconds = 30;
-```
-
-Out-of-range values are silently clamped. Set `warn = false` to suppress the
-log message.
-
-### `@SaveTo`
-
-Store a field in a separate file:
-
-```java
-@ConfigValue("secrets")
-@SaveTo("secrets.{ext}")
-private Secrets secrets = new Secrets();
-```
-
-### `@ConfigSerializable`
-
-Enable a class for use inside config lists or maps:
-
-```java
-@ConfigSerializable
-public class ServerEntry {
- @ConfigValue("name")
- private String name = "";
-}
-```
-
-### `@ListProcessor`
-
-Apply per-item validation when loading lists:
-
-```java
-@ConfigValue("entries")
-@ListProcessor(EntryProcessor.class)
-private List entries = new ArrayList<>();
-```
-
-The processor implements `ListItemProcessor` and returns `ProcessResult.ok()`,
-`ProcessResult.modified(value)`, or `ProcessResult.replaceWithDefault()`.
-
-## Hot Reload
-
-Mark reloadable sections and listen for updates:
-
-```java
-@ConfigReloadable(sections = {"messages"})
-public final class MyConfig { ... }
-```
-
-Use section-aware reloads to avoid rebuilding unrelated services:
-
-```java
-manager.reload(MyConfig.class, "messages");
-manager.reloadAsync(MyConfig.class, "messages");
-manager.reloadSmart(MyConfig.class, "messages");
-```
diff --git a/docs/modules/config.md b/docs/modules/config.md
deleted file mode 100644
index a6c531e78..000000000
--- a/docs/modules/config.md
+++ /dev/null
@@ -1,240 +0,0 @@
-# Config
-
-MagicUtils config maps files to POJOs using annotations while preserving user
-comments and unknown keys when saving.
-
-## Define A Config
-
-```java
-@ConfigFile("example.{ext}")
-@Comment("Example configuration")
-@ConfigReloadable(sections = {"messages"})
-public final class ExampleConfig {
- @ConfigValue("enabled")
- private boolean enabled = true;
-
- @ConfigSection("messages")
- private Messages messages = new Messages();
-
- public static final class Messages {
- @ConfigValue("greeting")
- @Comment("Greeting shown to players")
- private String greeting = "Hello";
- }
-}
-```
-
-```java
-ConfigManager manager = new ConfigManager(platform);
-ExampleConfig cfg = manager.register(ExampleConfig.class);
-```
-
-If your config path contains placeholders such as `{lang}` or `{service}`, pass
-them during registration:
-
-```java
-ExampleConfig cfg = manager.register(ExampleConfig.class, Map.of("service", "gateway"));
-```
-
-## Formats And Selection
-
-- JSON / JSONC are available out of the box.
-- YAML requires `magicutils-config-yaml`.
-- TOML requires `magicutils-config-toml`.
-
-If you use `{ext}` in `@ConfigFile`, MagicUtils chooses the extension from the
-current config format rules:
-
-- `.format` next to the config
-- `magicutils.format` in the root config directory
-- `-Dmagicutils.config.format=jsonc`
-- `MAGICUTILS_CONFIG_FORMAT`
-
-Fabric defaults to JSONC when no explicit format is selected.
-
-For advanced format selection and migrations, see
-[Config Advanced](config-advanced.md).
-
-## Reloading And Change Listeners
-
-Register a listener for live updates:
-
-```java
-ListenerSubscription subscription = manager.subscribeChanges(ExampleConfig.class, (config, sections) -> {
- // apply live updates
-});
-```
-
-`onChange(...)` is available as a convenience alias when you do not need the
-subscription handle.
-
-`@ConfigReloadable` restricts which sections can reload at runtime.
-
-## Threading Helpers
-
-Reloading touches disk. Use async or smart helpers on blocking-sensitive
-threads:
-
-```java
-manager.reloadAsync(cfg);
-manager.reloadAsync(ExampleConfig.class, "messages");
-manager.reloadAllAsync();
-
-manager.reloadSmart(cfg);
-manager.reloadSmart(ExampleConfig.class, "messages");
-manager.reloadAllSmart();
-```
-
-## Runtime-Managed Config Services
-
-When you already have `MagicRuntime`, you can bind a config-backed resource and
-let MagicUtils rebuild it automatically on matching config reloads:
-
-```java
-MagicRuntimeConfigBinding binding = runtime.bindConfig(
- "service.example",
- ExampleConfig.class,
- config -> new ReloadableService(config), // ReloadableService implements AutoCloseable
- "messages"
-);
-
-ReloadableService service = binding.require();
-```
-
-The bound service is also exposed as a named runtime component.
-
-## Migrations
-
-Config migrations are declared with `ConfigMigration` and tracked by the
-`config-version` key inside the file:
-
-```java
-manager.registerMigrations(ExampleConfig.class,
- new ConfigMigration() {
- public String fromVersion() { return "0"; }
- public String toVersion() { return "1"; }
- public void migrate(Map root) {
- root.put("enabled", true);
- }
- }
-);
-```
-
-## Validation Annotations
-
-### `@MinValue` / `@MaxValue`
-
-Clamp numeric fields to a safe range. Values outside the range are automatically
-adjusted when the config is loaded. A warning is logged by default.
-
-```java
-@ConfigValue("retry_interval")
-@MinValue(5)
-@Comment("Retry interval in seconds (minimum: 5)")
-private int retryInterval = 10;
-
-@ConfigValue("max_players")
-@MaxValue(100)
-@Comment("Maximum players (maximum: 100)")
-private int maxPlayers = 20;
-```
-
-Supported types: `byte`, `short`, `int`, `long`, `float`, `double` and their
-wrapper types.
-
-Set `warn = false` to suppress the clamping log message:
-
-```java
-@MinValue(value = 0, warn = false)
-```
-
-### `@DefaultValue`
-
-Provides a default string value for a config field when the key is missing from
-the file:
-
-```java
-@ConfigValue("channel")
-@DefaultValue("stable")
-private String channel;
-```
-
-For dynamic defaults, implement `DefaultValueProvider` and reference it:
-
-```java
-@ConfigValue("name")
-@DefaultValue(provider = MyDefaultProvider.class)
-private String name;
-```
-
-## Serializable Types
-
-### `@ConfigSerializable`
-
-Marks a class so it can be used in config lists and maps:
-
-```java
-@ConfigSerializable
-public class ServerEntry {
- @ConfigValue("name")
- private String name = "";
-
- @ConfigValue("port")
- private int port = 25565;
-}
-```
-
-Use `includeNulls = true` to serialize null fields explicitly.
-
-### `@SaveTo`
-
-Redirects a field to a different file:
-
-```java
-@ConfigValue("secrets")
-@SaveTo("secrets.{ext}")
-private Secrets secrets = new Secrets();
-```
-
-The path is relative to the plugin data folder.
-
-### `@ListProcessor`
-
-Applies per-item validation or transformation when loading list fields:
-
-```java
-@ConfigValue("servers")
-@ListProcessor(ServerListProcessor.class)
-private List servers = new ArrayList<>();
-```
-
-The processor implements `ListItemProcessor`:
-
-```java
-public class ServerListProcessor implements ListItemProcessor {
- @Override
- public ProcessResult process(ServerEntry item, int index) {
- if (item.name == null || item.name.isBlank()) {
- return ProcessResult.replaceWithDefault();
- }
- return ProcessResult.ok(item);
- }
-}
-```
-
-## Custom Value Adapters
-
-Register serializers via `ConfigAdapters.register(...)`:
-
-```java
-ConfigAdapters.register(Duration.class, new ConfigValueAdapter<>() {
- public Duration deserialize(Object value) { ... }
- public Object serialize(Duration value) { ... }
-});
-```
-
-## Shutdown
-
-Bootstrap helpers and `MagicRuntime` can manage the config manager lifecycle
-for you. In manual setups, call `ConfigManager.shutdown()` during plugin or mod
-shutdown to stop file watchers cleanly.
diff --git a/docs/modules/diagnostics.md b/docs/modules/diagnostics.md
deleted file mode 100644
index 33759be34..000000000
--- a/docs/modules/diagnostics.md
+++ /dev/null
@@ -1,97 +0,0 @@
-# Diagnostics
-
-MagicUtils diagnostics provide runtime self-checks that can be executed inside a
-live plugin or mod. The diagnostics service is built on top of `MagicRuntime`
-and ships with safe infrastructure-focused checks for runtime wiring,
-filesystem access, scheduler behavior, command registration exposure, and
-placeholder registry access.
-
-## Bootstrap Wiring
-
-Enable diagnostics from the platform bootstrap builder:
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)
- .enableCommands()
- .enableDiagnostics()
- .configureDiagnostics(registry -> {
- registry.register(new MyDatabaseCheck());
- })
- .buildRuntime();
-
-DiagnosticsService diagnostics = magic.diagnosticsService();
-```
-
-The service is also available from the runtime container:
-
-```java
-DiagnosticsService diagnostics =
- magic.runtime().requireComponent(DiagnosticsService.class);
-```
-
-## Running Checks
-
-Execute the full report or a single suite:
-
-```java
-DiagnosticReport safeReport = diagnostics.runAll(DiagnosticRunRequest.safe());
-DiagnosticReport standardReport = diagnostics.runSuite(
- "scheduler",
- DiagnosticRunRequest.standard()
-);
-```
-
-Built-in suites include:
-
-- `runtime`
-- `filesystem`
-- `config`
-- `scheduler`
-- `threading`
-- `commands`
-- `placeholders`
-
-`SAFE` mode avoids temp-file writes and reload probes. `STANDARD` mode enables
-reversible probes such as temp-file writes and config reloadability checks.
-
-## Exporting Reports
-
-Reports can be rendered to text or exported as JSON:
-
-```java
-List lines = DiagnosticReports.renderText(safeReport);
-Path exported = diagnostics.exportJson(safeReport);
-```
-
-The default export path is:
-
-```text
-/diagnostics/latest.json
-```
-
-## Command Helper
-
-Mount diagnostics into an existing command tree the same way you mount help:
-
-```java
-MagicCommand admin = MagicCommand.builder("admin")
- .subCommand(HelpCommandSupport.createHelpSubCommand(
- logger.getCore(),
- registry::commandManager
- ))
- .subCommand(DiagnosticsCommandSupport.createDiagnosticsSubCommand(
- logger.getCore(),
- magic::diagnosticsService
- ))
- .build();
-```
-
-Supported command forms:
-
-- `/plugin diagnostics`
-- `/plugin diagnostics safe`
-- `/plugin diagnostics standard`
-- `/plugin diagnostics export`
-- `/plugin diagnostics export standard`
-- `/plugin diagnostics suite magicutils.scheduler`
-- `/plugin diagnostics suite magicutils.scheduler standard`
diff --git a/docs/modules/http-client.md b/docs/modules/http-client.md
deleted file mode 100644
index 5e74a1427..000000000
--- a/docs/modules/http-client.md
+++ /dev/null
@@ -1,187 +0,0 @@
-# HTTP client
-
-MagicUtils HTTP client wraps `java.net.http.HttpClient` with config defaults,
-JSON mapping, retries, and a small multipart helper.
-
-## Dependency
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-http-client:{{ magicutils_version }}")
-}
-```
-
-## Configuration
-
-The module stores settings in `http-client.{ext}` (JSONC by default on Fabric).
-
-Key sections:
-
-- `timeouts`: connect + request timeouts
-- `retry`: backoff settings and retry status codes
-- `logging`: request/response logging toggles
-- `defaults`: base URL, headers, HTTP version
-
-## Basic usage
-
-```java
-Platform platform = new BukkitPlatformProvider(this);
-ConfigManager configManager = new ConfigManager(platform);
-
-MagicHttpClient client = MagicHttpClient.builder(platform, configManager)
- .baseUrl("https://api.example.com/")
- .header("Authorization", "Bearer token")
- .build();
-
-HttpResponse response = client.get("status");
-```
-
-## Runtime profiles
-
-When you already have a `MagicRuntime`, you can declare named HTTP/WebSocket
-profiles that rebuild automatically on config reload:
-
-```java
-MagicHttpClientProfile monitoring = MagicHttpClientProfile
- .builder(runtime, "http.monitoring", ApiConfig.class)
- .sections("monitoring")
- .baseUrl(config -> config.monitoring.baseUrl)
- .bearerAuth(config -> config.monitoring.token)
- .build();
-
-MagicHttpClient client = monitoring.require();
-MagicHttpClient sameClient = runtime.requireNamedComponent("http.monitoring", MagicHttpClient.class);
-```
-
-```java
-MagicWebSocketClientProfile gateway = MagicWebSocketClientProfile
- .builder(runtime, "ws.gateway", ApiConfig.class)
- .sections("gateway")
- .baseUrl(config -> config.gateway.baseUrl)
- .bearerAuth(config -> config.gateway.token)
- .subprotocols(config -> config.gateway.subprotocols)
- .build();
-```
-
-This removes the usual `close old client -> build new client -> swap references`
-boilerplate from service reload paths.
-
-## Smart methods
-
-Smart methods switch to async automatically on blocking-sensitive threads and
-return a `CompletableFuture`:
-
-```java
-client.getSmart("status").thenAccept(response -> {
- // ...
-});
-```
-
-## JSON to POJO
-
-```java
-public record StatusResponse(String status) {}
-
-StatusResponse response = client.getJson("status", StatusResponse.class);
-```
-
-## JSON POST
-
-```java
-public record CreateRequest(String name) {}
-
-client.postJson("items", new CreateRequest("Test"));
-```
-
-## Multipart upload
-
-```java
-MultipartBody body = MultipartBody.builder()
- .text("title", "Hello")
- .file("file", Path.of("logo.png"))
- .build();
-
-client.postMultipart("upload", body);
-```
-
-## WebSocket client
-
-`MagicWebSocketClient` wraps `java.net.http.WebSocket` with the same builder
-pattern and config integration as the HTTP client.
-
-### Basic usage
-
-```java
-MagicWebSocketClient wsClient = MagicWebSocketClient.builder(platform, configManager)
- .baseUrl("wss://api.example.com/ws")
- .header("Authorization", "Bearer token")
- .subprotocols(List.of("v1"))
- .build();
-
-CompletableFuture ws = wsClient.connectAsync("/events", myListener);
-```
-
-### Builder options
-
-The builder supports the same options as `MagicHttpClient.Builder`:
-
-- `baseUrl(...)` — base URL prepended to connect paths
-- `header(...)` / `headers(...)` — default headers
-- `userAgent(...)` — User-Agent header
-- `connectTimeout(...)` — connection timeout
-- `followRedirects(...)` — redirect policy
-- `subprotocols(...)` — WebSocket subprotocol list
-- `logger(...)` — platform logger for connection diagnostics
-- `config(...)` / `logging(...)` — shared `HttpClientConfig` settings
-- `mapper(...)` — custom Jackson `ObjectMapper`
-
-### Connect methods
-
-| Method | Description |
-| --- | --- |
-| `connect(path, listener)` | Synchronous connect (blocks). |
-| `connectAsync(path, listener)` | Returns `CompletableFuture`. |
-| `connectSmart(path, listener)` | Async on blocking-sensitive threads, sync otherwise. |
-
-### Runtime profiles
-
-Use `MagicWebSocketClientProfile` for config-aware WebSocket clients that
-rebuild on config reload:
-
-```java
-MagicWebSocketClientProfile gateway = MagicWebSocketClientProfile
- .builder(runtime, "ws.gateway", ApiConfig.class)
- .sections("gateway")
- .baseUrl(config -> config.gateway.wsUrl)
- .bearerAuth(config -> config.gateway.token)
- .subprotocols(config -> config.gateway.subprotocols)
- .build();
-
-MagicWebSocketClient client = gateway.require();
-```
-
-### Cleanup
-
-Call `wsClient.close()` to release resources. When using runtime profiles, the
-profile handles cleanup automatically on config reload and runtime shutdown.
-
-## Async method variants
-
-Every convenience method on `MagicHttpClient` has three variants:
-
-| Suffix | Behaviour |
-| --- | --- |
-| _(none)_ | Synchronous. Throws on blocking-sensitive threads. |
-| `...Async(...)` | Returns `CompletableFuture`. Always non-blocking. |
-| `...Smart(...)` | Sync when safe, async when on a blocking-sensitive thread. |
-
-Available methods: `get`, `getJson`, `post`, `postJson`, `postMultipart`,
-`send`.
-
-## Notes
-
-- Retries apply to the convenience methods (`get`, `post`, `postJson`, etc.).
-- For raw `send(...)`, retries are not automatic.
-- JSON mapping uses Jackson; invalid JSON throws `IllegalStateException`.
-- Synchronous methods throw `IllegalStateException` on blocking-sensitive
- threads. Use `...Async()` or `...Smart()` in handlers.
diff --git a/docs/modules/lang.md b/docs/modules/lang.md
deleted file mode 100644
index 36a5f961d..000000000
--- a/docs/modules/lang.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# Lang
-
-The lang module manages localisation files, custom messages, and per-player
-language overrides.
-
-## Setup
-
-Manual setup:
-
-```java
-LanguageManager languageManager = new LanguageManager(platform, configManager);
-languageManager.init("en");
-languageManager.setFallbackLanguage("en");
-languageManager.addMagicUtilsMessages();
-
-Messages.register("myplugin", languageManager);
-logger.setLanguageManager(languageManager);
-```
-
-Bootstrap helpers perform the same wiring automatically when language support is
-enabled.
-
-## File Layout And Formats
-
-Language files live under `lang/{lang}.{ext}` inside the platform config
-directory.
-
-- YAML is supported when `magicutils-config-yaml` is installed.
-- JSON / JSONC work out of the box.
-- TOML works when `magicutils-config-toml` is installed.
-
-`Messages.register(scope, manager)` keeps each plugin or mod isolated. Use
-`Messages.setLanguageManager(...)` only when you need the legacy global
-fallback.
-
-## Resolving Messages
-
-```java
-MessagesView messages = Messages.view("myplugin");
-
-Component title = messages.get("myplugin.welcome");
-messages.send(playerAudience, "myplugin.goodbye");
-
-String raw = messages.getRaw("myplugin.balance", "amount", "42");
-Component rich = messages.get("myplugin.balance", Map.of("amount", "42"));
-```
-
-`Messages` uses MiniMessage for rich output and supports `{placeholder}`-style
-replacements.
-
-## Per-Player Languages
-
-```java
-languageManager.setPlayerLanguage(playerUuid, "uk");
-String msg = languageManager.getMessageForAudience(audience, "myplugin.welcome");
-```
-
-`setPlayerLanguage(...)` accepts UUIDs, `Audience`, or player objects that
-expose `getUniqueId()`.
-
-## Custom Messages
-
-```java
-languageManager.putCustomMessage("en", "myplugin.welcome", "Hello");
-```
-
-Custom messages are persisted under the `messages` section of the active
-language file.
-
-## Async And Smart Loading
-
-Language loading touches disk. Use the async or smart helpers on
-blocking-sensitive threads:
-
-```java
-languageManager.loadLanguageAsync("en");
-languageManager.setLanguageAsync("en");
-languageManager.reloadAsync();
-
-languageManager.loadLanguageSmart("en");
-languageManager.setLanguageSmart("en");
-languageManager.reloadSmart();
-```
-
-## Fallbacks And Missing Keys
-
-```java
-languageManager.setFallbackLanguage("en");
-languageManager.setLogMissingMessages(true);
-```
-
-When the current language is missing a key, MagicUtils falls back to the
-configured fallback language before logging a missing-message warning.
diff --git a/docs/modules/logger-config.md b/docs/modules/logger-config.md
deleted file mode 100644
index 2d19d2c09..000000000
--- a/docs/modules/logger-config.md
+++ /dev/null
@@ -1,97 +0,0 @@
-# Logger config reference
-
-`logger.{ext}` stores logger settings. On Bukkit it lives in the plugin config
-folder. On Fabric it is namespaced under `config//` by default.
-
-Formats:
-
-- JSON/JSONC by default
-- YAML/TOML when the format helpers are available
-
-## Minimal example
-
-```yaml
-plugin-name: DonateMenu
-short-name: DM
-debug-placeholders: false
-
-prefix:
- chat-mode: FULL
- console-mode: SHORT
- custom: "[DM]"
- use-gradient-chat: true
- use-gradient-console: false
-
-defaults:
- target: BOTH
- text-max-length: 262144
- placeholder-engine-order: [MINI_PLACEHOLDERS, PB4, PAPI]
- miniplaceholders-mode: COMPONENT
- pb4-mode: COMPONENT
-
-chat:
- auto-generate-colors: true
- gradient: ["#7c3aed", "#ec4899"]
- colors:
- error: ["#ff4444", "#cc0000"]
- warn: ["#ffbb33", "#ff8800"]
- debug: ["#33b5e5", "#0099cc"]
- success: ["#00c851", "#007e33"]
-
-console:
- auto-generate-colors: true
- gradient: ["#ffcc00", "#ff6600"]
- strip-formatting: false
- colors:
- error: ["#ff4444", "#cc0000"]
- warn: ["#ffbb33", "#ff8800"]
- debug: ["#33b5e5", "#0099cc"]
- success: ["#00c851", "#007e33"]
-
-help:
- use-logger-colors: true
- primary-color: "#ff55ff"
- muted-color: "gray"
- text-color: "white"
- line: "-----------------------------"
- page-size: 7
- max-enum-values: 8
-
-sub-loggers:
- Commands:
- enabled: true
-```
-
-## Key sections
-
-### plugin-name / short-name
-
-Auto-filled on first run and used in prefix rendering. `short-name` is used by
-`PrefixMode.SHORT`.
-
-### prefix
-
-- `chat-mode` / `console-mode`: `NONE`, `SHORT`, `FULL`, `CUSTOM`
-- `custom`: custom prefix string for `CUSTOM`
-- `use-gradient-chat` / `use-gradient-console`: apply gradient to prefix
-
-### defaults
-
-- `target`: `CHAT`, `CONSOLE`, `BOTH`
-- `text-max-length`: max JSON length (Fabric only)
-- `placeholder-engine-order`: preferred external placeholder order
-- `miniplaceholders-mode`: `COMPONENT` or `TAG`
-- `pb4-mode`: `COMPONENT` or `RAW`
-
-### chat / console
-
-Color palettes and gradients. Set `auto-generate-colors` to `false` to force
-custom palettes.
-
-### help
-
-Controls the built-in help renderer formatting and pagination.
-
-### sub-loggers
-
-Per-prefix enable flags for `logger.withPrefix(...)` instances.
diff --git a/docs/modules/logger.md b/docs/modules/logger.md
deleted file mode 100644
index ea7036f63..000000000
--- a/docs/modules/logger.md
+++ /dev/null
@@ -1,164 +0,0 @@
-# Logger
-
-MagicUtils logger builds on Adventure components and provides a consistent API
-for console and chat output, with optional localisation, internal placeholders,
-and external placeholder engines.
-
-## Setup
-
-The recommended path is to obtain the logger from a bootstrap result or
-`MagicRuntime`.
-
-### Bukkit/Paper
-
-```java
-BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin)
- .buildRuntime();
-
-Logger logger = magic.logger();
-```
-
-### Fabric
-
-```java
-FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server)
- .buildRuntime();
-
-Logger logger = magic.logger();
-```
-
-### Velocity
-
-Velocity bootstrap returns `LoggerCore` directly:
-
-```java
-VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, "MyPlugin", dataDirectory)
- .buildRuntime();
-
-LoggerCore logger = magic.logger();
-```
-
-### NeoForge / Custom Platforms
-
-NeoForge and custom platforms typically wire `LoggerCore` manually:
-
-```java
-Platform platform = new NeoForgePlatformProvider();
-ConfigManager configManager = new ConfigManager(platform);
-LoggerCore logger = new LoggerCore(platform, configManager, this, "MyMod");
-```
-
-## Basic Usage
-
-```java
-logger.info("Ready.");
-logger.warn("Slow query detected");
-logger.error("Database unavailable");
-```
-
-The logger accepts MiniMessage markup and can target console, chat, or both.
-
-## Log Builder
-
-```java
-logger.info()
- .toConsole()
- .noPrefix()
- .send("Reloaded");
-```
-
-Use the builder when you want fine-grained control:
-
-- `target(LogTarget.CHAT | CONSOLE | BOTH)`
-- `to(audience)`
-- `toConsole()`
-- `noPrefix()`
-
-## Prefixed Loggers
-
-```java
-PrefixedLogger db = logger.withPrefix("database", "[DB]");
-db.info("Connected");
-```
-
-Each prefixed logger gets its own entry under `sub-loggers` in `logger.{ext}`
-for enable or disable toggles.
-
-## Prefix Modes
-
-Prefix rendering is controlled by `PrefixMode`:
-
-- `FULL` -> full plugin/mod name
-- `SHORT` -> short name from config
-- `CUSTOM` -> `setCustomPrefix(...)`
-- `NONE` -> no prefix
-
-`Logger` delegates these controls to the underlying `LoggerCore`.
-
-## Runtime Integration
-
-When you already have `MagicRuntime`, the logger is a shared typed component:
-
-```java
-MagicRuntime runtime = magic.runtime();
-LoggerCore loggerCore = runtime.requireComponent(LoggerCore.class);
-```
-
-That makes it easy to pass the logger into reloadable services or register
-named runtime resources that log through the same config and placeholder setup.
-
-## Logger Configuration
-
-`LoggerConfig` is stored as `logger.{ext}`.
-
-- Bukkit: plugin config directory
-- Fabric: `config//` by default
-- Velocity / custom platforms: resolved through the active `Platform`
-
-See [Logger Config](logger-config.md) for a full key reference.
-
-Key sections:
-
-- `prefix`
-- `defaults`
-- `chat` / `console`
-- `help`
-- `sub-loggers`
-
-## Localisation
-
-Attach a `LanguageManager` to enable `@key` lookups:
-
-```java
-logger.setLanguageManager(languageManager);
-logger.info("@myplugin.ready");
-```
-
-The logger resolves the key through the attached `LanguageManager` and then
-renders the result as MiniMessage.
-
-Bootstrap helpers bind the language manager automatically when language support
-is enabled.
-
-## Internal And External Placeholders
-
-Logger messages pass through both:
-
-1. `MagicPlaceholders` for `{key}` and `{namespace:key}` tokens.
-2. Platform-specific external placeholder engines.
-
-Examples:
-
-```java
-logger.info("Balance: {economy:balance}");
-logger.info("Hello {player}");
-```
-
-Platform integrations:
-
-- Bukkit logger installs the PlaceholderAPI bridge and a Bukkit external
- placeholder engine.
-- Fabric logger installs Text Placeholder API / MiniPlaceholders / PB4 support
- when those mods are present.
-- `LoggerCore` also supports a custom `ExternalPlaceholderEngine` for custom
- platforms.
diff --git a/docs/modules/overview.md b/docs/modules/overview.md
deleted file mode 100644
index 96c9ad54c..000000000
--- a/docs/modules/overview.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# Module Overview
-
-MagicUtils is split into focused artifacts so you can depend on only what you
-need. The platform adapters wire the shared modules to Bukkit/Paper, Fabric,
-Velocity, or NeoForge.
-
-Bootstrap helpers are available on Bukkit, Fabric, and Velocity. NeoForge
-currently uses manual wiring with the platform adapter plus command module.
-
-## Core Modules
-
-| Module | Artifact | Notes |
-| --- | --- | --- |
-| Platform API | `magicutils-api` | `Platform`, `Audience`, `TaskScheduler`, player lifecycle/message events, and shared interfaces. |
-| Core | `magicutils-core` | Shared runtime container (`MagicRuntime`), reflective access helpers, plus core config/lang/logger/placeholder wiring. |
-| HTTP client | `magicutils-http-client` | HTTP and WebSocket client wrappers with JSON, retries, multipart, and runtime profiles. |
-| Config | `magicutils-config` | JSON/JSONC config engine, migrations, comments. |
-| Config YAML | `magicutils-config-yaml` | Adds YAML support via Jackson. |
-| Config TOML | `magicutils-config-toml` | Adds TOML support via Jackson. |
-| Lang | `magicutils-lang` | Language manager and message helpers. |
-| Commands | `magicutils-commands` | Annotation/builder command framework. |
-| Diagnostics | `magicutils-diagnostics` | Runtime diagnostics service, built-in checks, JSON export, and command helper support. |
-| Logger | `magicutils-logger` | Adventure-based logger core. |
-| Placeholders | `magicutils-placeholders` | Placeholder registry. |
-| Processor | `magicutils-processor` | Annotation processor used by internal tooling. |
-
-## Platform Adapters
-
-| Platform | Artifact | Notes |
-| --- | --- | --- |
-| Bukkit/Paper | `magicutils-bukkit` | Includes the shared stack and exposes `BukkitBootstrap`. |
-| Bukkit bundle | `magicutils-bukkit-bundle` | Shared Bukkit/Paper plugin bundle for server installs. |
-| Fabric | `magicutils-fabric` | Platform API adapter for Fabric modular setups. |
-| Velocity | `magicutils-velocity` | Platform adapter plus config/logger/lang/commands for Velocity. |
-| NeoForge | `magicutils-neoforge` | Platform API adapter for NeoForge manual setups. |
-
-## Brigadier Integrations
-
-| Module | Artifact | Notes |
-| --- | --- | --- |
-| Brigadier base | `magicutils-commands-brigadier` | Shared Brigadier command registry base. |
-| Commands (NeoForge) | `magicutils-commands-neoforge` | Brigadier integration for NeoForge. |
-
-## Fabric Integrations
-
-| Module | Artifact | Notes |
-| --- | --- | --- |
-| Logger (Fabric) | `magicutils-logger-fabric` | Fabric logger adapter. |
-| Commands (Fabric) | `magicutils-commands-fabric` | Brigadier integration plus `FabricBootstrap`. |
-| Placeholders (Fabric) | `magicutils-placeholders-fabric` | Fabric placeholder bridge. |
-| Fabric bundle | `magicutils-fabric-bundle` | Jar-in-jar distribution for shared server installs. |
-
-Use the per-module pages for API details, examples, and configuration options.
diff --git a/docs/modules/permissions.md b/docs/modules/permissions.md
deleted file mode 100644
index 3209e42c1..000000000
--- a/docs/modules/permissions.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# Permissions
-
-MagicUtils commands generate and evaluate permission nodes automatically. The
-registry prefix controls namespacing, while annotations control explicit nodes,
-defaults, and conditional argument checks.
-
-## Permission Prefix
-
-Every command registry has a permission prefix. You set it either directly on
-the registry:
-
-```java
-CommandRegistry registry = CommandRegistry.create(plugin, "donatemenu", logger);
-```
-
-or through the bootstrap helper:
-
-```java
-BukkitBootstrap.forPlugin(plugin)
- .permissionPrefix("donatemenu")
- .enableCommands()
- .buildRuntime();
-```
-
-MagicUtils prepends that prefix to all generated nodes.
-
-## Generated Nodes
-
-When annotations omit explicit permission strings, MagicUtils builds defaults:
-
-- Command: `commands.`
-- Subcommand: `commands..subcommand.`
-- Argument: `commands..subcommand..argument.`
-
-With prefix `donatemenu`:
-
-- `donatemenu.commands.donate`
-- `donatemenu.commands.donate.subcommand.give`
-- `donatemenu.commands.donate.subcommand.give.argument.player`
-
-## Default Access
-
-`MagicPermissionDefault` controls what happens when the permission node is not
-granted explicitly:
-
-- `TRUE` -> everyone
-- `OP` -> operators or elevated senders
-- `NOT_OP` -> non-operators / non-elevated senders
-- `FALSE` -> nobody
-
-Platform behaviour differs slightly:
-
-- Bukkit registers permission nodes with Bukkit's permission manager and uses
- Bukkit permission defaults.
-- Fabric checks `fabric-permissions-api-v0` when available and falls back to
- op-level checks.
-- NeoForge falls back to command-source permission level checks.
-- Velocity relies on the proxy's permission checks and uses the default policy
- only when the node itself is absent.
-
-## Wildcards
-
-On Bukkit, MagicUtils also registers wildcard nodes:
-
-- `...commands..*`
-- `...commands..subcommand.*`
-
-Velocity also honours prefix-style wildcard checks such as `prefix.*` and
-`prefix.*` when the proxy reports them as granted.
-
-## Explicit Permission Annotations
-
-Use explicit nodes when you want stable names independent of the generated
-shape:
-
-```java
-@CommandInfo(
- name = "donate",
- permission = "donatemenu.open",
- permissionDefault = MagicPermissionDefault.TRUE
-)
-public final class DonateCommand extends MagicCommand {
-}
-```
-
-Subcommands support the same fields:
-
-```java
-@SubCommand(
- name = "reload",
- permission = "donatemenu.admin.reload",
- permissionDefault = MagicPermissionDefault.OP
-)
-public CommandResult reload(@Sender MagicSender sender) {
- return CommandResult.success("Reloaded");
-}
-```
-
-## Argument Permissions
-
-Use `@Permission` on parameters to gate argument usage:
-
-```java
-public CommandResult grant(
- @Sender MagicSender sender,
- @Permission(when = "other(player)") @ParamName("player") Player target
-) {
- return CommandResult.success("ok");
-}
-```
-
-You can override the generated node segment:
-
-```java
-@Permission(node = "target", includeArgumentSegment = false)
-```
-
-## Conditional Permission Keywords
-
-- `self(arg)` / `other(arg)` / `anyother(arg)`
-- `not_null(arg)` / `exists(arg)`
-- `distinct(a,b)` / `all_distinct(a,b)`
-- `equals(a,b)` / `not_equals(a,b)`
-
-Use `compare = CompareMode.UUID/NAME/EQUALS/AUTO` to control how values are
-compared.
-
-## Manual Checks
-
-`MagicSender` exposes direct permission checks when you need custom logic
-outside annotation processing:
-
-```java
-MagicSender sender = MagicSender.wrap(rawSender);
-if (MagicSender.hasPermission(rawSender, "donatemenu.commands.donate")) {
- // ...
-}
-```
-
-When the platform adapter uses op-level fallback semantics, the two-argument
-form keeps the adapter default, while the three-argument form overrides it for
-that one check:
-
-```java
-if (MagicSender.hasPermission(rawSender, "leavepulse.whitelist.notify", 3)) {
- // ...
-}
-
-if (sender != null && sender.hasPermission("leavepulse.admin", 4)) {
- // ...
-}
-```
-
-This matters most on Fabric and NeoForge, where permission backends may fall
-back to command-source op levels when a node is unknown or no backend responds.
-
-You can also use the registry prefix when building related manual nodes so the
-manual and generated permissions stay in the same namespace.
diff --git a/docs/modules/placeholders-advanced.md b/docs/modules/placeholders-advanced.md
deleted file mode 100644
index d6c953753..000000000
--- a/docs/modules/placeholders-advanced.md
+++ /dev/null
@@ -1,120 +0,0 @@
-# Placeholders Advanced
-
-This page covers namespace metadata, listeners, local scopes, and platform
-integration details.
-
-## Namespace Metadata
-
-Register namespace metadata to expose author and version information:
-
-```java
-MagicPlaceholders.registerNamespace("donatemenu", "THEROER", "1.10.0");
-```
-
-Metadata is consumed by PlaceholderAPI expansions and Fabric namespace
-registration layers.
-
-## Key Normalization
-
-Namespaces and keys are normalized to lowercase. Use lowercase names to avoid
-accidental duplicates.
-
-## Arguments
-
-Resolvers receive an optional `argument` string.
-
-PlaceholderAPI-style examples:
-
-- `%donatemenu_balance%`
-- `%donatemenu_balance:bank%`
-
-Inside `MagicPlaceholders.render(...)`, the default argument separator is `|`:
-
-- `{balance|bank}`
-- `{donatemenu:balance}`
-- `{donatemenu:balance:bank}`
-
-Override the local separator per context when needed:
-
-```java
-PlaceholderContext context = PlaceholderContext.builder()
- .argumentSeparator("::")
- .build();
-```
-
-## Audience Handling
-
-`Audience` can be `null` for console or offline contexts. Use
-`MagicPlaceholders.audienceFromUuid(UUID)` when you only have a player ID:
-
-```java
-MagicPlaceholders.register("donatemenu", "balance", (audience, arg) -> {
- if (audience == null) {
- return "0";
- }
- return "42";
-});
-```
-
-## Local And Global Placeholders
-
-Global placeholders are namespace-free:
-
-```java
-MagicPlaceholders.registerGlobal("server", (audience, arg) -> "Example");
-```
-
-Local placeholders are tied to an owner key such as a plugin instance, a
-logger, or another stable runtime object:
-
-```java
-MagicPlaceholders.registerLocal(this, "balance", (audience, arg) -> "42");
-```
-
-This is useful when you want one placeholder key to mean different things in
-different runtime scopes.
-
-## Registry Listeners
-
-Listen to registry changes:
-
-```java
-MagicPlaceholders.addListener(new MagicPlaceholders.PlaceholderListener() {
- public void onPlaceholderRegistered(MagicPlaceholders.PlaceholderKey key) {
- // refresh cache, update UI, etc.
- }
-
- public void onPlaceholderUnregistered(MagicPlaceholders.PlaceholderKey key) {
- }
-
- public void onNamespaceUpdated(String namespace) {
- }
-});
-```
-
-## Debug Listeners
-
-Subscribe to resolution events:
-
-```java
-MagicPlaceholders.addDebugListener((ownerKey, key, audience, arg, result) -> {
- // inspect result.value()
-});
-```
-
-This is especially useful when debugging placeholder chains inside logger
-output.
-
-## Safe Resolution
-
-`MagicPlaceholders.resolve(...)` returns an empty string when the placeholder is
-missing or when a resolver throws. Keep resolvers fast and side-effect free.
-
-## Integration Summary
-
-- Bukkit: auto-bridges to [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi)
- when the Bukkit logger integration is active.
-- Fabric: registers with [PB4 Placeholder API](https://placeholders.pb4.eu/)
- when present.
-- Fabric: registers with [Text Placeholder API](https://modrinth.com/mod/placeholder-api)
- and [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders) when present.
diff --git a/docs/modules/placeholders.md b/docs/modules/placeholders.md
deleted file mode 100644
index 1fd079aa4..000000000
--- a/docs/modules/placeholders.md
+++ /dev/null
@@ -1,104 +0,0 @@
-# Placeholders
-
-The placeholder module offers a shared registry plus platform bridges for:
-
-- Bukkit PlaceholderAPI
-- Fabric PB4 Placeholder API
-- Fabric Text Placeholder API
-- Fabric MiniPlaceholders
-
-## Register Placeholders
-
-```java
-MagicPlaceholders.registerNamespace("myplugin", "MyPlugin", "1.0.0");
-
-MagicPlaceholders.register("myplugin", "online", (audience, arg) -> "42");
-MagicPlaceholders.register("myplugin", "balance", (audience, arg) -> {
- return arg != null ? arg : "0";
-});
-```
-
-Global placeholders are available without a namespace:
-
-```java
-MagicPlaceholders.registerGlobal("server", (audience, arg) -> "Example");
-```
-
-Local placeholders are scoped to an owner key:
-
-```java
-MagicPlaceholders.registerLocal(this, "balance", (audience, arg) -> "42");
-```
-
-## Resolve And Render
-
-Resolve a single entry directly:
-
-```java
-String value = MagicPlaceholders.resolve("myplugin", "online", audience, null);
-```
-
-Render tokens inside text:
-
-```java
-PlaceholderContext context = PlaceholderContext.builder()
- .audience(audience)
- .defaultNamespace("myplugin")
- .ownerKey(this)
- .inline(Map.of("player", "Steve"))
- .build();
-
-String text = "Hello {player}! Balance: {balance|bank}";
-String rendered = MagicPlaceholders.render(context, text);
-```
-
-Convenience overloads:
-
-```java
-String rendered = MagicPlaceholders.render(audience, "Hello {server}");
-```
-
-## Resolution Order
-
-For `{key}` tokens:
-
-1. Inline values from the context
-2. Local placeholders for the `ownerKey`
-3. Default namespace (`defaultNamespace:key`)
-4. Global placeholders
-
-Namespaced tokens use `{namespace:key}` or `{namespace:key:arg}`. For local
-arguments in plain `{key}` form, use the argument separator (default `|`):
-
-```java
-PlaceholderContext context = PlaceholderContext.builder()
- .argumentSeparator("::")
- .build();
-```
-
-## Platform Bridges
-
-- Bukkit: the bridge is installed when the Bukkit `Logger` adapter is created
- or when Bukkit bootstrap wiring creates that logger for you.
-- Fabric: the bridge is installed by the Fabric logger integration and registers
- available placeholders with PB4 Placeholder API, Text Placeholder API, and
- MiniPlaceholders when those mods are present.
-
-Supported Fabric mods:
-
-- [PB4 Placeholder API](https://placeholders.pb4.eu/)
-- [Text Placeholder API](https://modrinth.com/mod/placeholder-api)
-- [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders)
-
-## Registry Introspection
-
-Snapshots of the registry are available for tooling and debugging:
-
-```java
-Set namespaces = MagicPlaceholders.namespaces();
-Map entries =
- MagicPlaceholders.entries();
-```
-
-See [Placeholders Advanced](placeholders-advanced.md) for metadata, listeners,
-and debug hooks.
diff --git a/docs/platforms/bukkit.md b/docs/platforms/bukkit.md
deleted file mode 100644
index c736fcedb..000000000
--- a/docs/platforms/bukkit.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Bukkit/Paper
-
-Use `magicutils-bukkit` to wire MagicUtils to Bukkit/Paper.
-
-If you want a shared install for multiple plugins, use
-`magicutils-bukkit-bundle` as a standalone plugin and add a dependency on it
-in your `plugin.yml` (`depend: [MagicUtils]` or `softdepend`).
-
-## Recommended Bootstrap
-
-```java
-public final class MyPlugin extends JavaPlugin {
- private BukkitBootstrap.RuntimeResult magic;
-
- @Override
- public void onEnable() {
- magic = BukkitBootstrap.forPlugin(this)
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new ExampleCommand()))
- .buildRuntime();
- }
-
- @Override
- public void onDisable() {
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- }
-}
-```
-
-`build()` returns the legacy bootstrap view. `buildRuntime()` additionally gives
-you a managed `MagicRuntime`.
-
-## Manual Wiring
-
-```java
-Platform platform = new BukkitPlatformProvider(plugin);
-ConfigManager configManager = new ConfigManager(platform);
-Logger logger = new Logger(platform, plugin, configManager);
-```
-
-Use the manual path only when you need full control over how the services are
-created.
-
-## Commands
-
-`BukkitBootstrap.enableCommands()` creates a `CommandRegistry` for the plugin.
-The registry registers commands directly with the Bukkit `CommandMap`, so you do
-not need to declare them in `plugin.yml`.
-
-Permission nodes are resolved using the prefix passed to
-`permissionPrefix(...)` or, by default, the plugin name.
-
-## Placeholders
-
-When [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi) is installed,
-MagicUtils placeholders can be bridged into PlaceholderAPI through the Bukkit
-integration layer.
diff --git a/docs/platforms/core.md b/docs/platforms/core.md
deleted file mode 100644
index fbc353bf7..000000000
--- a/docs/platforms/core.md
+++ /dev/null
@@ -1,215 +0,0 @@
-# Core / Common Logic
-
-Use this page when your plugin or mod has a thin platform entrypoint and most of
-the real logic lives in shared `common` or `core` code.
-
-That usually means:
-
-- Bukkit/Fabric/Velocity/NeoForge only bootstrap the runtime
-- config, lang, logger, HTTP, placeholders, and business logic live in common
-- platform modules stay as adapters instead of owning the feature logic
-
-## Typical Module Layout
-
-```text
-my-plugin/
-|- common/
-|- bukkit/
-|- fabric/
-`- velocity/
-```
-
-In this structure:
-
-- `bukkit/`, `fabric/`, and `velocity/` bootstrap MagicUtils for their runtime
-- `common/` receives `MagicRuntime` and owns the actual feature logic
-- platform modules do not reimplement the same services three times
-
-## The Recommended Split
-
-Keep these parts in the platform layer:
-
-- bootstrap helpers such as `BukkitBootstrap`, `FabricBootstrap`,
- `VelocityBootstrap`
-- platform event registration
-- command registration against the platform dispatcher
-- external placeholder bridge setup
-- plugin or mod lifecycle entrypoints
-
-Keep these parts in `common` or other shared modules:
-
-- services built on `MagicRuntime`
-- config models and reload logic
-- logger and language driven messaging
-- HTTP clients and runtime config bindings
-- placeholder logic used by your own code
-- business rules that should work across every platform
-
-## Common Code Should Depend On Shared Abstractions
-
-Inside common code, depend on `MagicRuntime` or the core services it exposes:
-
-- `Platform`
-- `ConfigManager`
-- `LoggerCore`
-- `LanguageManager`
-- named runtime resources and config bindings
-
-This keeps the shared layer free from Bukkit, Fabric, Velocity, or NeoForge
-classes.
-
-## Wiring Shared Services From The Platform Layer
-
-The platform entrypoint should bootstrap MagicUtils and hand the runtime to your
-common services:
-
-```java
-public final class MyPlugin extends JavaPlugin {
- private BukkitBootstrap.RuntimeResult magic;
- private CommonBootstrap bootstrap;
-
- @Override
- public void onEnable() {
- magic = BukkitBootstrap.forPlugin(this)
- .enableCommands()
- .buildRuntime();
-
- bootstrap = new CommonBootstrap(magic.runtime());
- bootstrap.start();
- }
-
- @Override
- public void onDisable() {
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- }
-}
-```
-
-The same structure works on Fabric, Velocity, and NeoForge: platform code
-creates the runtime, shared code consumes it.
-
-`forPlugin(...)`, `forMod(...)`, and the other bootstrap helpers belong to the
-platform module only. `common` should consume the resulting runtime, not create
-it.
-
-## Example Shared Service
-
-```java
-public final class CommonBootstrap {
- private final MagicRuntime runtime;
- private final ConfigManager configManager;
- private final LoggerCore logger;
- private final Optional languages;
-
- public CommonBootstrap(MagicRuntime runtime) {
- this.runtime = runtime;
- this.configManager = runtime.configManager();
- this.logger = runtime.logger();
- this.languages = runtime.findComponent(LanguageManager.class);
- }
-
- public void start() {
- logger.info("Starting shared services");
-
- languages.ifPresent(manager -> logger.info("Language manager is available"));
-
- runtime.bindConfig(
- "http.backend",
- BackendConfig.class,
- config -> MagicHttpClient.builder(runtime.platform(), configManager)
- .baseUrl(config.baseUrl)
- .build(),
- "backend"
- );
- }
-}
-```
-
-This is the typical multi-platform pattern: the shared layer receives one
-runtime from the adapter layer and builds everything else on top of it.
-
-## What Works Well In Common
-
-The `magicutils-core` path is especially good for:
-
-- `MagicRuntime`
-- `ConfigManager`
-- `LoggerCore`
-- optional `LanguageManager`
-- runtime-managed HTTP or WebSocket clients
-- shared placeholder evaluation
-- reloadable services built with `bindConfig(...)`
-
-If your modules already target multiple platforms, this is usually where the
-majority of the code should live.
-
-## Player Events
-
-`Platform` provides normalized player lifecycle and message events that work
-across all platforms without importing Bukkit, Fabric, or Velocity types.
-
-### Player Lifecycle
-
-Subscribe to join/leave events:
-
-```java
-ListenerSubscription sub = platform.subscribePlayerLifecycle(event -> {
- if (event.type() == PlayerLifecycleType.JOIN) {
- logger.info(event.playerName() + " joined");
- }
-});
-```
-
-`PlayerLifecycle` contains:
-
-- `playerId()` — player UUID (when available)
-- `playerName()` — display/login name
-- `type()` — `JOIN` or `LEAVE`
-
-### Player Messages
-
-Subscribe to chat messages and commands:
-
-```java
-ListenerSubscription sub = platform.subscribePlayerMessages(event -> {
- if (event.type() == PlayerMessageType.CHAT) {
- logger.info(event.playerName() + ": " + event.message());
- }
-});
-```
-
-`PlayerMessage` contains:
-
-- `playerId()` — player UUID (when available)
-- `playerName()` — display/login name
-- `message()` — raw chat content or command line
-- `type()` — `CHAT` or `COMMAND`
-
-Both subscriptions return `ListenerSubscription` which can be closed to
-unsubscribe. Both records expose `isValid()` for null-safety checks.
-
-## What Should Stay Out Of Common
-
-Try not to leak platform-specific APIs into the shared layer.
-
-Avoid putting these directly into `common`:
-
-- Bukkit `JavaPlugin`, Fabric callbacks, Velocity annotations, NeoForge events
-- platform command dispatcher registration
-- direct calls to platform plugin managers or server APIs
-- external bridge setup that only exists on one runtime
-
-Keep those in the platform module and pass only the shared abstractions
-downward.
-
-## If You Really Need A Custom Platform
-
-That is a separate case from normal `common` code.
-
-If you are actually building a new adapter around `Platform`,
-`ShutdownHookRegistrar`, or your own bootstrap path, use the Runtime guide and
-the platform API as the source of truth, but keep that adapter layer small and
-let the feature logic remain in shared services.
diff --git a/docs/platforms/fabric.md b/docs/platforms/fabric.md
deleted file mode 100644
index afa9fa0a4..000000000
--- a/docs/platforms/fabric.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# Fabric
-
-For most mods, use `magicutils-fabric-bundle` and wire the runtime through
-`FabricBootstrap`.
-
-## Recommended Bootstrap
-
-```java
-public final class MyMod implements ModInitializer {
- private static final String MOD_ID = "mymod";
-
- private MinecraftServer server;
- private FabricBootstrap.RuntimeResult magic;
-
- @Override
- public void onInitialize() {
- magic = FabricBootstrap.forMod(MOD_ID, () -> server)
- .enableCommands()
- .buildRuntime();
-
- CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
- if (magic.commandRegistry() != null) {
- magic.commandRegistry().registerCommand(dispatcher, new ExampleCommand());
- }
- });
-
- ServerLifecycleEvents.SERVER_STARTED.register(server -> this.server = server);
- ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
- this.server = null;
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- });
- }
-}
-```
-
-`FabricBootstrap` wires `Platform`, `ConfigManager`, `Logger`,
-`LanguageManager`, `Messages`, and an optional Fabric command registry. Actual
-command registration still happens in Fabric's Brigadier callback.
-
-## Modular Setup
-
-If you do not want the bundle, combine the platform adapter with the Fabric
-integration modules you need:
-
-- `magicutils-fabric`
-- `magicutils-commands-fabric`
-- `magicutils-logger-fabric`
-- `magicutils-placeholders-fabric`
-
-## Permissions
-
-Fabric permissions integrate with `fabric-permissions-api-v0` when installed.
-If no permission provider exists, MagicUtils falls back to op-level checks. Use
-`opLevel(...)` on `FabricBootstrap` or `CommandRegistry.create(...)` when you
-need a different default operator level.
-
-## Placeholders
-
-Fabric placeholder support is optional and activates automatically when
-placeholder mods are present:
-
-- [Text Placeholder API](https://modrinth.com/mod/placeholder-api)
-- [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders)
-
-## Config Format
-
-Default config format on Fabric is JSONC. You can override it via
-`.format` or `magicutils.format` (see the Config module docs).
-
-## Bundle Strategy
-
-- Embed `magicutils-fabric-bundle` in your mod.
-- Or ship one shared bundle mod on the server and depend on it.
-
-Do not use both approaches at the same time.
diff --git a/docs/platforms/neoforge.md b/docs/platforms/neoforge.md
deleted file mode 100644
index 8f003e559..000000000
--- a/docs/platforms/neoforge.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# NeoForge
-
-NeoForge support currently uses the manual wiring path with
-`magicutils-neoforge` and the optional `magicutils-commands-neoforge` module.
-
-## Platform Wiring
-
-```java
-public final class MyMod {
- private static final String MOD_ID = "mymod";
-
- private final Platform platform;
- private final ConfigManager configManager;
- private final LoggerCore logger;
- private final CommandRegistry commands;
-
- public MyMod() {
- platform = new NeoForgePlatformProvider();
- configManager = new ConfigManager(platform);
- logger = new LoggerCore(platform, configManager, this, "MyMod");
- commands = CommandRegistry.create(MOD_ID, MOD_ID, logger);
- }
-
- @SubscribeEvent
- public void onRegisterCommands(RegisterCommandsEvent event) {
- commands.registerCommand(event.getDispatcher(), new ExampleCommand());
- }
-}
-```
-
-If you need a different operator threshold, use
-`CommandRegistry.create(..., opLevel)`.
-
-## Notes
-
-- NeoForge uses `LoggerCore` directly instead of the Bukkit/Fabric `Logger`
- wrapper.
-- There is no dedicated NeoForge bootstrap helper yet.
-- There is no NeoForge placeholder bridge yet. Use MagicUtils core
- placeholders directly where needed.
diff --git a/docs/platforms/velocity.md b/docs/platforms/velocity.md
deleted file mode 100644
index fe535ea8b..000000000
--- a/docs/platforms/velocity.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# Velocity
-
-`magicutils-velocity` exposes platform wiring, config, logger, lang, and
-command integration in one artifact.
-
-## Dependency
-
-```kotlin
-dependencies {
- implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}")
-}
-```
-
-## Recommended Bootstrap
-
-```java
-@Plugin(id = "myplugin", name = "MyPlugin", version = "1.0.0")
-public final class MyPlugin {
- private final ProxyServer proxy;
- private final org.slf4j.Logger slf4j;
- private final Path dataDirectory;
- private VelocityBootstrap.RuntimeResult magic;
-
- @Inject
- public MyPlugin(ProxyServer proxy,
- org.slf4j.Logger slf4j,
- @DataDirectory Path dataDirectory) {
- this.proxy = proxy;
- this.slf4j = slf4j;
- this.dataDirectory = dataDirectory;
- }
-
- @Subscribe
- public void onProxyInitialize(ProxyInitializeEvent event) {
- magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory)
- .slf4j(slf4j)
- .enableCommands()
- .configureCommands(registry -> registry.registerCommand(new ExampleCommand()))
- .buildRuntime();
- }
-
- @Subscribe
- public void onProxyShutdown(ProxyShutdownEvent event) {
- if (magic != null) {
- magic.runtime().close();
- magic = null;
- }
- }
-}
-```
-
-`VelocityBootstrap` creates the `Platform`, `ConfigManager`, `LoggerCore`,
-`LanguageManager`, and optional `CommandRegistry` for the plugin.
-
-## Commands
-
-Velocity commands register directly with the proxy command manager.
-
-- Use `enableCommands()` to create the registry during bootstrap.
-- Use `configureCommands(...)` to register commands right away.
-- Use `asyncExecutor(...)` when you want to override the default async executor
- used by the command layer.
-
-## Bootstrap Options
-
-`VelocityBootstrap.Builder` supports additional configuration beyond the basics:
-
-| Method | Description |
-| --- | --- |
-| `slf4j(logger)` | Bind SLF4J logger for console output. |
-| `enableCommands()` | Create a `CommandRegistry` during bootstrap. |
-| `permissionPrefix(prefix)` | Set the permission node prefix for commands. |
-| `asyncExecutor(executor)` | Override the async executor used by the command layer. |
-| `configureCommands(consumer)` | Register commands during bootstrap. |
-| `initLanguage(boolean)` | Enable/disable language manager initialization. |
-| `bindLoggerLanguage(boolean)` | Bind the language manager to the logger. |
-| `setMessagesManager(boolean)` | Set the global `Messages` language manager. |
-| `registerMessages(boolean)` | Register the plugin's messages scope. |
-| `addMagicUtilsMessages(boolean)` | Register built-in MagicUtils messages. |
-| `translations(consumer)` | Configure additional translations. |
-
-## Player Events
-
-Velocity supports the platform-agnostic player lifecycle and message events:
-
-```java
-platform.subscribePlayerLifecycle(event -> {
- if (event.type() == PlayerLifecycleType.JOIN) {
- logger.info(event.playerName() + " connected");
- }
-});
-```
-
-See [Core / Common Logic](core.md#player-events) for the full event API.
-
-## Notes
-
-- `Platform.runOnMain(...)` executes immediately because Velocity has no main
- game thread.
-- `buildRuntime()` returns a managed `MagicRuntime` and wires shutdown cleanup
- through the plugin instance you pass to the platform provider.
diff --git a/docs/recipes/index.md b/docs/recipes/index.md
deleted file mode 100644
index 7d2075753..000000000
--- a/docs/recipes/index.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Recipes
-
-## Embed MagicUtils In A Fabric Mod
-
-Use `include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}")`
-and avoid installing the standalone bundle on the server.
-
-## Share A Single Bundle On A Server
-
-Install `magicutils-fabric-bundle` in `mods/` and use `modImplementation`
-without `include(...)`.
-
-Download link:
-[`magicutils-fabric-bundle-{{ magicutils_version }}.jar`](https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-fabric-bundle/{{ magicutils_version }}/magicutils-fabric-bundle-{{ magicutils_version }}.jar)
-
-## Register The Built-In Help Command
-
-Bukkit or Fabric:
-
-```java
-registry.registerCommand(new HelpCommand(logger, registry));
-```
-
-## Add Help As A Subcommand
-
-Use this pattern when you want help attached to an existing command tree or on
-platforms without a dedicated `HelpCommand` wrapper:
-
-```java
-registry.registerCommand(new MyCommand()
- .addSubCommand(HelpCommandSupport.createHelpSubCommand(
- "help",
- loggerCore,
- registry::commandManager
- )));
-```
-
-## Force A Config Format
-
-Create `.format` next to the file or `magicutils.format` in the root
-config directory with a single line:
-
-```text
-jsonc
-```
-
-## Add Enum Suggestions
-
-Enum parameters are auto-suggested. You can additionally limit visible choices
-via `@Suggest("{one,two}")` for a specific argument.
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index f0fd01a3b..000000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,88 +0,0 @@
-site_name: MagicUtils
-site_description: Modular toolkit for Bukkit/Paper, Fabric, Velocity, and NeoForge.
-site_url: https://THEROER.github.io/MagicUtils/
-repo_name: THEROER/MagicUtils
-repo_url: https://github.com/THEROER/MagicUtils
-edit_uri: edit/main/docs/
-
-theme:
- name: material
- language: en
- features:
- - navigation.tabs
- - navigation.sections
- - navigation.top
- - content.code.copy
- - content.tabs.link
- - toc.follow
- palette:
- - scheme: slate
- primary: deep purple
- accent: purple
- toggle:
- icon: material/weather-sunny
- name: Switch to light mode
- - scheme: default
- primary: deep purple
- accent: purple
- toggle:
- icon: material/weather-night
- name: Switch to dark mode
-
-plugins:
- - search
- - macros:
- module_name: macros
-
-markdown_extensions:
- - admonition
- - attr_list
- - md_in_html
- - pymdownx.details
- - pymdownx.superfences
- - pymdownx.highlight:
- anchor_linenums: true
- - pymdownx.inlinehilite
- - pymdownx.tabbed:
- alternate_style: true
- - pymdownx.snippets
- - pymdownx.keys
- - toc:
- permalink: true
-
-extra:
- version:
- provider: mike
-
-nav:
- - Home: index.md
- - Getting Started:
- - Installation: getting-started/installation.md
- - Quickstart: getting-started/quickstart.md
- - Runtime: getting-started/runtime.md
- - Migration Guide: getting-started/migration.md
- - Versioning: getting-started/versioning.md
- - Modules:
- - Overview: modules/overview.md
- - Logger: modules/logger.md
- - Logger Config: modules/logger-config.md
- - Commands: modules/commands.md
- - Permissions: modules/permissions.md
- - Commands Cheat Sheet: modules/commands-cheatsheet.md
- - Diagnostics: modules/diagnostics.md
- - HTTP Client: modules/http-client.md
- - Config: modules/config.md
- - Config Advanced: modules/config-advanced.md
- - Lang: modules/lang.md
- - Placeholders: modules/placeholders.md
- - Placeholders Advanced: modules/placeholders-advanced.md
- - Platforms:
- - Core / Common Logic: platforms/core.md
- - Bukkit/Paper: platforms/bukkit.md
- - Fabric: platforms/fabric.md
- - Velocity: platforms/velocity.md
- - NeoForge: platforms/neoforge.md
- - Recipes: recipes/index.md
- - API: api.md
- - FAQ: faq.md
- - Contributing: contributing.md
diff --git a/requirements-docs.txt b/requirements-docs.txt
deleted file mode 100644
index c52339370..000000000
--- a/requirements-docs.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-mkdocs-material
-mkdocs-macros-plugin
-mike
diff --git a/scripts/README.md b/scripts/README.md
index 71b1c10cb..606c896aa 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -12,8 +12,8 @@ group, implemented in `build-logic/src/main/kotlin/MagicUtilsReleaseTasks.kt`
- `./gradlew smokeTest -Pversion=X.Y.Z` — poll the published POM.
- `./gradlew release -Pversion=X.Y.Z` — preflight → bump → dispatch.
-The server-side chain (tagging, docs/javadoc dispatch, gh-pages publish) still
-lives in `.github/workflows/release.yml`.
+The server-side chain (tagging, docs/javadoc dispatch, Reposilite Maven publish)
+lives in `.github/workflows/release.yml` and `publish-maven.yml`.
For the full release process — pipeline diagram, troubleshooting, and
verification commands — see [`../RELEASING.md`](../RELEASING.md).
]