Raisu Developer Guide
Raisu is a platform-agnostic debug snapshot library for Minecraft servers. Collect structured data from your server, encrypt it, upload it to a paste service, and share a compact shortcode that the Raisu frontend renders entirely in the browser.
The library is modular — integrate at the Paper, Spigot, or standalone level. You register categories (groups of related data) and components (individual data widgets) that compose each category. When you call snapshot(), every registered provider is evaluated and the result is packaged for sharing.
How it works
When you call raisu.encode(snapshot, config), the library runs this pipeline server-side:
Serialize
Categories and components are encoded to a compact MessagePack binary.
Encrypt
The binary is encrypted with a randomly generated AES-128-CBC key and prepended with the IV.
Upload & pack
The ciphertext is base64-encoded and uploaded to your paste provider. A base64url shortcode is returned bundling the provider ID, paste key, and raw AES key.
The Raisu frontend reverses this pipeline client-side: decodes the shortcode, fetches the ciphertext, decrypts it with the embedded AES key, deserializes MessagePack, and renders each category and component. No data is sent to any external backend.
Modules
| Artifact | Purpose |
|---|---|
raisu-bootstrap | Version management — rarely needed directly |
raisu-api | Core interfaces — use as compileOnly or api dependency |
raisu-core | Engine — AES encryption, MessagePack encoding, paste upload |
raisu-spigot | Spigot/Bukkit integration with built-in debug categories |
raisu-paper | Paper integration — extends Spigot with TPS/MSPT via raisu:performance |
For most Minecraft plugins, depend on raisu-paper (Paper servers) or raisu-spigot (Spigot/Bukkit). Both transitively include raisu-core and raisu-api.
Installation
Raisu is distributed via JitPack. Select your build tool and module below — the snippet is generated for you.
repositories {
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.Reddishye.raisu:raisu-paper:v1.0.0")
}For most Minecraft plugins, depend on raisu-paper (Paper servers) or raisu-spigot (Spigot/Bukkit). Both transitively include raisu-core and raisu-api.
Quick start — Paper plugin
Creates a Raisu instance with all built-in categories: raisu:system, raisu:memory, raisu:threads, raisu:plugins, raisu:performance.
public final class MyPlugin extends JavaPlugin {
private Raisu raisu;
@Override
public void onEnable() {
raisu = PaperRaisu.create(this);
}
public void onDebugCommand() {
EncodeConfig config = EncodeConfig.builder()
.provider(PasteProvider.PASTES_DEV)
.build();
String shortcode = raisu.encode(raisu.snapshot(), config);
getLogger().info("Debug snapshot: https://yoursite.com/view#" + shortcode);
}
}Quick start — Spigot plugin
Same API as the Paper integration, without the Paper-specific performance category.
raisu = SpigotRaisu.create(this); // same API, no raisu:performance categoryQuick start — Standalone
Implement RaisuPlatform directly for any non-Bukkit environment. Only serverVersion() is required.
RaisuPlatform platform = new RaisuPlatform() {
@Override public String serverVersion() { return "MyApp 1.0"; }
};
Raisu raisu = Raisu.create(platform);Static categories
Use static categories when the data is known at registration time and doesn't change between snapshots.
Category myCategory = Category.builder()
.id("my:info")
.name(Component.text("My Info"))
.icon("📦")
.priority(10)
.add(/* component */)
.build();
raisu.register(myCategory);Dynamic providers
Use registerProvider when data changes over time. The lambda is called each time raisu.snapshot() is invoked.
Keep provider lambdas fast — avoid blocking I/O inside them.
raisu.registerProvider("my:stats", () -> {
int activeTasks = scheduler.getActiveTasks();
return Category.builder()
.id("my:stats")
.name(Component.text("Task Stats"))
.icon("⚙️")
.priority(10)
.add(/* KV, Table, etc. */)
.build();
});Builder pattern
Mix built-in and custom categories using the platform builder. To disable all raisu:* defaults, call .excludeDefaultCategories().
raisu = PaperRaisu.builder(this)
// Add your own alongside the defaults:
.addCategory(myStaticCategory)
.addProvider("my:economy", () -> buildEconomyCategory())
// Or start from scratch (disables all raisu:* built-ins):
// .excludeDefaultCategories()
.build();Component types (V1)
All component interfaces live in com.redactado.raisu.component. V1 types are available from raisu-api version 1.
| Wire type | Java interface | Fields |
|---|---|---|
KEY_VALUE | KeyValue | key, value (Strings) |
TEXT | Text | content (String) |
TABLE | Table | headers (String[]), rows (String[][]) |
LIST | List | items (String[]) |
PROGRESS_BAR | ProgressBar | label, current, max (doubles) |
GRAPH | Graph | title, dataPoints (ordered String→Double map) |
TREE | Tree | root (recursive TreeNode with label + children) |
Creating V1 components
Public factory methods on component interfaces are planned for a future release. Until then, use the *Impl classes from raisu-core directly.
import com.redactado.raisu.core.component.*;
new KeyValueImpl("Server TPS", "19.95")
new TextImpl("Everything looks healthy.")
new ProgressBarImpl("Memory", usedMb, maxMb)
new TableImpl(List.of("Name", "Value"), rows)
new ListImpl(List.of("item1", "item2"))
new GraphImpl("TPS History", dataPoints) // dataPoints = LinkedHashMap
new TreeImpl(new TreeNodeImpl("root", children))V2 component system
The V2 component system extends V1 with two new categories of primitives: layout containers that compose child components, and rich display widgets for richer server-status UIs.
V2 components are fully backward-compatible. Snapshots containing V2 components are identified by version: 2 in the payload. Older frontend versions will render them as best-effort.
Containers that arrange child components: COLUMN, ROW, GRID, PANEL
Rich data widgets: BADGE, STAT, ALERT, CODE_BLOCK, LOG_VIEW, TIMELINE, SPARKLINE, GAUGE, LINK, IFRAME
V2 — Layout components
Layout components are containers. Their children field holds an ordered array of child RaisuComponent objects. Nesting any depth is supported.
All layout builders share Gap (NONE | SMALL | MEDIUM | LARGE) and Alignment (START | CENTER | END | STRETCH) enums, serialized as their name strings.
Stacks children vertically.
| Field | Type | Description |
|---|---|---|
alignment | Alignment | START | CENTER | END | STRETCH |
gap | Gap | NONE | SMALL | MEDIUM | LARGE |
children | Component[] | Child components, top to bottom |
Column.builder()
.gap(Gap.MEDIUM)
.add(new KeyValueImpl("Status", "Online"))
.add(new ProgressBarImpl("Memory", usedMb, maxMb))
.build()Arranges children horizontally. Each child gets equal flex width.
| Field | Type | Description |
|---|---|---|
alignment | Alignment | Cross-axis child alignment |
gap | Gap | Spacing between children |
wrap | boolean | Whether children wrap to the next line |
children | Component[] | Child components, left to right |
Row.builder()
.gap(Gap.SMALL)
.add(Badge.of("ONLINE", Severity.SUCCESS))
.add(Badge.of("Paper 1.21.4", Severity.INFO))
.build()Places children in a CSS grid with a fixed column count (1–4).
| Field | Type | Description |
|---|---|---|
columns | int | Number of columns (1–4) |
gap | Gap | Gap between cells |
children | Component[] | Child components, filling left-to-right |
Grid.builder(3)
.gap(Gap.MEDIUM)
.add(Stat.builder("CPU", "32%").unit("%").build())
.add(Stat.builder("RAM", "4.2").unit("GB").build())
.add(Stat.builder("TPS", "19.9").build())
.build()A titled card container. Supports optional collapsing.
| Field | Type | Description |
|---|---|---|
title | String | Panel header label |
collapsible | boolean | Whether the panel can be collapsed by the user |
collapsed | boolean | Initial collapsed state |
children | Component[] | Child components inside the panel |
Panel.builder("Network Stats")
.collapsible(true)
.add(new KeyValueImpl("Bytes In", "12.4 MB"))
.add(new KeyValueImpl("Bytes Out", "3.1 MB"))
.build()V2 — Display components
Display components are leaf nodes — they render a single piece of data without children.
A small pill label with semantic colour. Ideal for status indicators. SUCCESS and ERROR badges pulse.
| Field | Type | Values |
|---|---|---|
text | String | Label text |
severity | Severity | DEFAULT INFO SUCCESS WARNING ERROR |
Badge.of("ONLINE", Severity.SUCCESS)
Badge.of("DEGRADED", Severity.WARNING)
Badge.of("OFFLINE", Severity.ERROR)
Badge.of("Paper 1.21.4", Severity.INFO)A big-number metric with optional unit and trend indicator.
| Field | Type | Description |
|---|---|---|
label | String | Metric name |
value | String | Formatted value string |
unit | String (optional) | Unit suffix (ms, MB, %, …) |
trend | double (optional) | Positive = up (green), negative = down (red), null = hidden |
description | String (optional) | Subtitle / context line below the value |
Stat.builder("Active Players", "142").trend(+3.0).build()
Stat.builder("Avg MSPT", "2.4").unit("ms").description("last 5 min avg").build()An alert callout with severity level, optional title, and message.
| Field | Type | Values / Description |
|---|---|---|
severity | Severity | DEFAULT | INFO | SUCCESS | WARNING | ERROR |
title | String (optional) | Bold header line |
message | String | Body text |
Alert.of(Severity.WARNING, "High Memory", "Heap usage exceeds 85%.")
Alert.of(Severity.SUCCESS, "All systems nominal.")A copyable code block with a language label. The frontend does not apply syntax highlighting to code blocks received from the server (highlighting is reserved for the docs page).
| Field | Type | Description |
|---|---|---|
content | String | Raw source code string |
language | String | Language hint shown in the header (e.g. java, yaml) |
CodeBlock.of("max-players: 100\nonline-mode: true", "yaml")A scrollable log panel. Lines containing ERROR/WARN/INFO are coloured automatically.
| Field | Type | Description |
|---|---|---|
entries | LogEntry[] | Ordered log entries |
entries[].timestamp | long | Epoch milliseconds |
entries[].severity | Severity | DEFAULT | INFO | SUCCESS | WARNING | ERROR |
entries[].message | String | Log message text |
LogView.builder()
.add(System.currentTimeMillis(), Severity.INFO, "Server started")
.add(System.currentTimeMillis(), Severity.WARNING, "High GC pressure")
.add(System.currentTimeMillis(), Severity.ERROR, "Plugin failed to load")
.build()An ordered list of timestamped events connected by a vertical rail.
| Field | Type | Description |
|---|---|---|
events | TimelineEvent[] | Ordered list of events (oldest first) |
events[].label | String | Event title |
events[].description | String | Detail / context text |
events[].timestamp | long | Epoch milliseconds — formatted as local time in the UI |
Timeline.builder()
.add("Server started", "PaperMC 1.21.4", startMs)
.add("Plugins loaded", "42 plugins enabled", startMs + 3000)
.add("World loaded", "overworld / 8192 chunks", startMs + 5000)
.build()A mini inline line chart with the latest value prominently displayed. Trend colour (green/red) is derived from first vs. last value.
| Field | Type | Description |
|---|---|---|
label | String | Metric name |
values | double[] | Ordered data points (oldest → newest) |
unit | String (optional) | Unit suffix |
new SparklineImpl("TPS (1 min buckets)", tpsHistory, null)A labelled progress track that transitions from violet → amber → red as the value approaches max.
| Field | Type | Description |
|---|---|---|
label | String | Gauge label |
current | double | Current value |
max | double | Maximum value (min is always 0) |
unit | String (optional) | Unit suffix |
Gauge.of("Heap", usedMb, maxMb)
Gauge.builder("Heap", usedMb, maxMb).unit("MB").build()A clickable hyperlink. Set external: true to open in a new tab with rel="noopener noreferrer".
| Field | Type | Description |
|---|---|---|
label | String | Link display text |
url | String | Destination URL (http/https URLs open in a new tab automatically) |
Link.of("View on GitHub", "https://github.com/...")Embeds an external web page inside the snapshot. The frame is sandboxed (allow-scripts allow-same-origin allow-forms allow-popups) and lazy-loaded. Use this to embed dashboards, status pages, or any URL-accessible resource directly into a category.
| Field | Type | Description |
|---|---|---|
url | String | URL of the page to embed |
title | String? | Optional label shown above the frame |
height | int? | Frame height in pixels (default: 400) |
// Minimal — embed a URL at the default 400 px height
Iframe.of("https://status.example.com")
// With label and custom height
Iframe.of("https://grafana.example.com/d/xyz", "Grafana Dashboard", 600)
// Via Components factory
Components.iframe("https://status.example.com", "Service Status", 300)Taking a snapshot
// Quick — includes all registered categories and providers:
Snapshot snap = raisu.snapshot();
// Manual — build exactly what you want:
Snapshot snap = raisu.snapshotBuilder()
.serverVersion("MyApp 2.0")
.javaVersion(System.getProperty("java.version"))
.addCategory(myCategory)
.build();Encoding & sharing
The encode call serialises the snapshot to MessagePack, encrypts it with AES-128-CBC, uploads the ciphertext to the chosen paste provider, and returns a compact base64url shortcode.
EncodeConfig config = EncodeConfig.builder()
.provider(PasteProvider.PASTES_DEV) // or HASTEBIN
.build();
String shortcode = raisu.encode(snap, config);
// Share as: https://yoursite.com/view#<shortcode>Unregistering
raisu.unregister("my:info"); // remove static category
raisu.unregisterProvider("my:stats"); // remove dynamic providerBuilt-in categories
raisu-spigot defaults
| Category ID | Icon | Priority | Description |
|---|---|---|---|
raisu:system | 🖥️ | 1 | OS name/version/arch, Java version/vendor/VM, server software, player count |
raisu:memory | 💾 | 2 | Heap usage progress bar, heap used/total/free/max (MB), CPU count |
raisu:threads | 🧵 | 3 | Active thread count, thread name/state/type/priority table |
raisu:plugins | 🔌 | 4 | Plugin count, name/version/enabled-status table |
raisu-paper additions
| Category ID | Icon | Priority | Description |
|---|---|---|---|
raisu:performance | 📊 | 5 | TPS progress bar (1m), TPS 1m/5m/15m, average MSPT |
RaisuPlatform contract
Implementing RaisuPlatform is all that's needed for any non-Bukkit environment. All defaults are sensible — only serverVersion() is required.
public interface RaisuPlatform {
String serverVersion(); // required
default String javaVersion() { ... } // optional override
default String platformName() { ... } // optional override
default Logger logger() { ... } // optional override
default void logInfo(String msg) { ... } // derived from logger()
default void logWarning(String msg) { ... } // derived from logger()
}Snapshot versioning
The MessagePack payload includes a version integer. Payloads produced by older backends have no version key and are treated as version 0.
| Version | Status | Notes |
|---|---|---|
0 (absent) | ✓ Supported | Legacy payloads — no version field in the map |
1 | ✓ Supported | V1 components only — explicit version: 1 |
2 | ✓ Supported | V2 layout (COLUMN/ROW/GRID/PANEL) + display (BADGE/STAT/ALERT/…) components |
| 3+ | ⚠ Best-effort | Unknown versions render with a warning banner in the UI |
Library consumers do not need to interact with version numbers directly. The frontend handles all parsing and version detection automatically.
HTTP API
The Raisu frontend exposes a server-side JSON endpoint that accepts a shortcode, runs the full fetch → decrypt → deserialize pipeline, and returns the decoded snapshot. Use this to programmatically access snapshot data without reimplementing the decode logic yourself.
/api/snapshotDecodes a snapshot shortcode and returns the full JSON payload. The server handles fetch, AES-CBC decryption, and MessagePack deserialisation. Response is cached for 30 seconds.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | The base64url shortcode from the viewer URL hash (everything after #) |
Responses
| Status | Body | When |
|---|---|---|
200 OK | application/json — Snapshot object | Shortcode decoded successfully |
400 Bad Request | {"error": "Missing 'code' query parameter"} | code param absent |
500 Internal Server Error | {"error": "..."} | Fetch, decrypt, or parse failed |
Example request:
GET /api/snapshot?code=AABB...200 response shape:
{
"version": 2,
"timestamp": 1734123456789,
"serverVersion": "Paper 1.21.4",
"javaVersion": "21.0.3",
"categories": [
{
"id": "raisu:system",
"name": "{"text":"System"}",
"icon": "💻",
"priority": 0,
"components": [
{
"type": "KEY_VALUE",
"data": { "key": "OS", "value": "Linux 6.1.0" }
},
{
"type": "STAT",
"data": {
"label": "Uptime",
"value": "14",
"unit": "days",
"trend": null,
"description": null
}
}
]
}
]
}The name field is an Adventure Component serialised as JSON — parse it with JSON.parse() and read .text for the plain display string.
Library summary for LLMs
This section provides machine-readable context about the Raisu library for use by language models, code assistants, and automated tooling. The information below is structured for easy parsing.
// RAISU — MACHINE-READABLE LIBRARY SUMMARY
// =========================================
// Library: Raisu
// Type: Java/Kotlin library for Minecraft server debug snapshots
// Language: Java 17+ (primary API), Kotlin-friendly
// Repository: JVM — Gradle/Maven
// PURPOSE:
// Raisu lets you capture structured server state as a "snapshot" —
// a collection of named categories, each containing typed data
// components. The snapshot is serialized (MessagePack), encrypted
// (AES-128-CBC), uploaded to a paste service, and shared as a
// compact base64url shortcode. The companion web frontend decrypts
// and renders it entirely in the browser (no server-side rendering).
// INTEGRATION POINTS:
// - PaperRaisu.create(plugin) → Paper/Spigot plugin entry point
// - SpigotRaisu.create(plugin) → Spigot/Bukkit entry point
// - Raisu.create(platform) → Standalone / custom platform
// - raisu.snapshot() → Collect all registered categories
// - raisu.encode(snap, config) → Serialize, encrypt, upload → shortcode
// - raisu.register(category) → Register a static Category
// - raisu.registerProvider(id, () -> category) → Register a dynamic provider
// GRADLE DEPENDENCY (Paper):
// implementation("com.redactado:raisu-paper:1.0.0-SNAPSHOT")
// PASTE PROVIDERS:
// PasteProvider.PASTES_DEV → https://api.pastes.dev
// PasteProvider.HASTEBIN → https://hastebin.com
// SHORTCODE FORMAT (base64url):
// byte[0] = provider ID (0=pastes.dev, 1=hastebin)
// byte[1] = paste key length
// byte[2..2+kLen] = paste key (ASCII)
// byte[2+kLen..] = raw AES-128 key (16 bytes)Wire format specification
The decrypted payload is a MessagePack map. The following pseudo-schema describes all known fields.
// PAYLOAD SCHEMA (MessagePack map, pseudo-TypeScript notation)
// ============================================================
interface Payload {
version: int // 0 (absent=legacy), 1, 2
timestamp: long // Unix epoch ms
serverVersion: string // e.g. "Paper 1.21.4"
javaVersion: string // e.g. "21.0.3"
categories: Category[] // ordered by priority asc
}
interface Category {
id: string // e.g. "raisu:system", "my:custom"
name: string // Adventure Component JSON or plain string
icon: string // emoji character
priority: int // lower = appears first
components: Component[] // ordered array of component objects
}
interface Component {
type: string // wire type constant (see below)
data: object // type-specific payload (see Component Reference)
}
// CATEGORY NAME FORMATS (Adventure Component JSON):
// Plain string: "My Category"
// JSON string: ""My Category""
// Object: {"text": "My Category"}
// Array: [{"text": "My Category"}]Shortcode decoding (client-side):
// SHORTCODE DECODE (TypeScript/browser)
function decodeShortcode(shortcode: string): { providerId, pasteKey, aesKey } {
// 1. base64url → bytes (replace - with +, _ with /)
// 2. bytes[0] = provider ID
// 3. bytes[1] = paste key byte length (kLen)
// 4. bytes[2..2+kLen] = UTF-8 paste key
// 5. bytes[2+kLen..] = raw 16-byte AES-128 key
}
// DECRYPTION (WebCrypto)
// algorithm: AES-CBC
// key length: 128 bits (16 bytes)
// IV: first 16 bytes of the encrypted blob
// ciphertext: remaining bytes after IVComponent type reference
Complete list of all component wire types and their data shapes. Use this as ground truth when generating or parsing Raisu snapshot data.
// ── V1 COMPONENT TYPES ───────────────────────────────────────────────────────
KEY_VALUE { key: string, value: string }
TEXT { content: string }
TABLE { headers: string[], rows: string[][] }
LIST { items: string[] }
PROGRESS_BAR { label: string, current: double, max: double }
GRAPH { title: string, dataPoints: Map<string, double> } // ordered map
TREE { root: TreeNode }
// TreeNode: { label: string, children: TreeNode[] }
// ── V2 SHARED ENUMS ──────────────────────────────────────────────────────────
// All serialized as their .name() string (uppercase).
// Severity: DEFAULT | INFO | SUCCESS | WARNING | ERROR
// Gap: NONE | SMALL | MEDIUM | LARGE
// Alignment: START | CENTER | END | STRETCH
// ── V2 LAYOUT TYPES ──────────────────────────────────────────────────────────
// Layout containers hold children: Component[]. Nesting is fully supported.
// "children" is the wire field name (NOT "components").
COLUMN { alignment: Alignment, gap: Gap, children: Component[] }
ROW { alignment: Alignment, gap: Gap, wrap: boolean, children: Component[] }
GRID { columns: int, gap: Gap, children: Component[] } // columns: 1..4
PANEL { title: string, collapsible: boolean, collapsed: boolean, children: Component[] }
// ── V2 DISPLAY TYPES ─────────────────────────────────────────────────────────
// "?" = nullable — packed as msgpack nil when absent.
// Map header size is always fixed (same number of keys regardless of nulls).
BADGE { text: string, severity: Severity }
STAT { label: string, value: string, unit: string|nil, trend: double|nil, description: string|nil }
// trend: positive = up (green), negative = down (red), null = hidden
ALERT { severity: Severity, title: string|nil, message: string }
CODE_BLOCK { content: string, language: string }
LOG_VIEW { entries: LogEntry[] }
// LogEntry: { timestamp: int64 (epoch ms), severity: Severity, message: string }
TIMELINE { events: TimelineEvent[] }
// TimelineEvent: { label: string, description: string, timestamp: int64 (epoch ms) }
SPARKLINE { label: string, values: double[], unit: string|nil } // oldest → newest
GAUGE { label: string, current: double, max: double, unit: string|nil }
// min is always 0; pct = current / max
LINK { label: string, url: string }
// http/https URLs are opened in a new tab automatically
IFRAME { url: string, title: string|nil, height: int|nil }
// height defaults to 400 px; frame is sandboxed
// ── GAUGE COLOUR THRESHOLDS ───────────────────────────────────────────────────
// pct = current / max
// pct > 0.80 → red (critical)
// pct > 0.60 → amber (warning)
// else → violet (normal)
// ── LOG_VIEW SEVERITY COLOURS ─────────────────────────────────────────────────
// ERROR → red-400
// WARNING → amber-400
// SUCCESS → emerald-400
// INFO → blue-400
// DEFAULT → zinc-400When generating Raisu category data programmatically (e.g. from an LLM), ensure type exactly matches one of the constants above (uppercase, underscores). Unknown types are silently skipped by the frontend renderer.
Example minimal snapshot (JSON representation of the msgpack):
{
"version": 2,
"timestamp": 1700000000000,
"serverVersion": "Paper 1.21.4",
"javaVersion": "21.0.3",
"categories": [
{
"id": "my:status",
"name": "Server Status",
"icon": "🖥️",
"priority": 1,
"components": [
{ "type": "BADGE", "data": { "text": "Online", "severity": "SUCCESS" } },
{ "type": "STAT", "data": { "label": "Players", "value": "42", "unit": null, "trend": 3.0, "description": null } },
{ "type": "GAUGE", "data": { "label": "Heap", "current": 512, "max": 2048, "unit": "MB" } },
{ "type": "ALERT", "data": { "severity": "WARNING", "title": null, "message": "High GC pressure detected." } }
]
}
]
}Live showcase
Open the interactive component showcase to see every V2 component rendered with real data. Use it to debug layouts, verify wire format correctness, and spot visual regressions.
Open component showcase → /debug-example-codeThe showcase is a static mock snapshot — no shortcode needed. It covers all 9 display types, all 4 layout containers, all 5 severities, and both collapsed and open Panel states.
Severity system
Five components use the shared Severity enum: BADGE, ALERT, and all log/timeline entries. Choose severity based on meaning, not aesthetics.
| Severity | Color | When to use |
|---|---|---|
DEFAULT | zinc (gray) | Neutral info, no urgency |
INFO | blue | Informational — something happened |
SUCCESS | emerald (green) | Positive outcome, goal reached |
WARNING | amber | Potential problem, needs attention |
ERROR | red | Something failed, action required |
SUCCESS and ERROR badges render a pulsing status dot to draw the eye to live operational state. Use them sparingly — if everything is SUCCESS, the signal is lost.
Severity in code (Java / Kotlin):
// All five severity values
Badge.of("Online", Severity.SUCCESS)
Badge.of("Degraded", Severity.WARNING)
Badge.of("Offline", Severity.ERROR)
Badge.of("Maintenance", Severity.INFO)
Badge.of("Unknown", Severity.DEFAULT)
// Alerts also use severity
Alert.of(Severity.ERROR, "OOM Imminent",
"Heap above 90%. Investigate memory leaks.")
Alert.of(Severity.WARNING,
"TPS dropped below 18.0 during world save at 14:32.")Layout patterns
Layout components (COLUMN, ROW, GRID, PANEL) are containers — they hold other components recursively. Display components are the leaf nodes that show actual data.
GRID — side-by-side metrics
Use GRID when you have 2–4 comparable stats. Pair with columns: 2 for most dashboards.
Grid.builder(2).gap(Gap.MEDIUM)
.add(Stat.builder("Players", String.valueOf(online)).build())
.add(Stat.builder("TPS", String.format("%.2f", tps)).build())
.add(Stat.builder("Loaded chunks", String.valueOf(chunks)).build())
.add(Stat.builder("Entity count", String.valueOf(entities)).build())
.build()ROW — badge strips
Use ROW with wrap: true for a horizontal strip of badges — server tags, feature flags, active modules.
Row.builder().alignment(Alignment.CENTER).gap(Gap.SMALL).wrap(true)
.add(Badge.of("Paper 1.21.4", Severity.DEFAULT))
.add(Badge.of("Java 21", Severity.INFO))
.add(Badge.of("Docker", Severity.INFO))
.build()PANEL — grouped sub-sections
Use PANEL to group related components under a heading. Set collapsible: true, collapsed: true for secondary details that would clutter the default view.
// Always visible
Panel.builder("Active Worlds")
.add(Stat.builder("overworld", worldStats("overworld")).build())
.add(Stat.builder("nether", worldStats("nether")).build())
.build()
// Collapsed by default — advanced details
Panel.builder("Advanced GC Stats").collapsible(true).collapsed(true)
.add(Gauge.builder("Eden", eden, edenMax).unit("MB").build())
.add(Gauge.builder("Old gen", old, oldMax).unit("MB").build())
.build()Layout components render their children directly — they inherit the category card's divide-y separator between siblings. Avoid deeply nesting COLUMN inside COLUMN; prefer a flat structure with GRID or ROW.
Gap & Alignment enums
| Enum | Values |
|---|---|
Gap | NONE SMALL MEDIUM LARGE |
Alignment | START CENTER END STRETCH |