Raisu
Documentation
Introduction

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.

Reddishye/raisuv1.0.0

How it works

When you call raisu.encode(snapshot, config), the library runs this pipeline server-side:

01

Serialize

Categories and components are encoded to a compact MessagePack binary.

02

Encrypt

The binary is encrypted with a randomly generated AES-128-CBC key and prepended with the IV.

03

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

ArtifactPurpose
raisu-bootstrapVersion management — rarely needed directly
raisu-apiCore interfaces — use as compileOnly or api dependency
raisu-coreEngine — AES encryption, MessagePack encoding, paste upload
raisu-spigotSpigot/Bukkit integration with built-in debug categories
raisu-paperPaper 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.

ver
kotlin
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.

java
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.

java
raisu = SpigotRaisu.create(this);   // same API, no raisu:performance category

Quick start — Standalone

Implement RaisuPlatform directly for any non-Bukkit environment. Only serverVersion() is required.

java
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.

java
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.

java
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().

java
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 typeJava interfaceFields
KEY_VALUEKeyValuekey, value (Strings)
TEXTTextcontent (String)
TABLETableheaders (String[]), rows (String[][])
LISTListitems (String[])
PROGRESS_BARProgressBarlabel, current, max (doubles)
GRAPHGraphtitle, dataPoints (ordered String→Double map)
TREETreeroot (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.

java
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))
Components V2

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.

Layout

Containers that arrange child components: COLUMN, ROW, GRID, PANEL

Display

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.

COLUMN

Stacks children vertically.

FieldTypeDescription
alignmentAlignmentSTART | CENTER | END | STRETCH
gapGapNONE | SMALL | MEDIUM | LARGE
childrenComponent[]Child components, top to bottom
java
Column.builder()
    .gap(Gap.MEDIUM)
    .add(new KeyValueImpl("Status", "Online"))
    .add(new ProgressBarImpl("Memory", usedMb, maxMb))
    .build()
ROW

Arranges children horizontally. Each child gets equal flex width.

FieldTypeDescription
alignmentAlignmentCross-axis child alignment
gapGapSpacing between children
wrapbooleanWhether children wrap to the next line
childrenComponent[]Child components, left to right
java
Row.builder()
    .gap(Gap.SMALL)
    .add(Badge.of("ONLINE", Severity.SUCCESS))
    .add(Badge.of("Paper 1.21.4", Severity.INFO))
    .build()
GRID

Places children in a CSS grid with a fixed column count (1–4).

FieldTypeDescription
columnsintNumber of columns (1–4)
gapGapGap between cells
childrenComponent[]Child components, filling left-to-right
java
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()
PANEL

A titled card container. Supports optional collapsing.

FieldTypeDescription
titleStringPanel header label
collapsiblebooleanWhether the panel can be collapsed by the user
collapsedbooleanInitial collapsed state
childrenComponent[]Child components inside the panel
java
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.

BADGE

A small pill label with semantic colour. Ideal for status indicators. SUCCESS and ERROR badges pulse.

FieldTypeValues
textStringLabel text
severitySeverityDEFAULT INFO SUCCESS WARNING ERROR
java
Badge.of("ONLINE", Severity.SUCCESS)
Badge.of("DEGRADED", Severity.WARNING)
Badge.of("OFFLINE", Severity.ERROR)
Badge.of("Paper 1.21.4", Severity.INFO)
STAT

A big-number metric with optional unit and trend indicator.

FieldTypeDescription
labelStringMetric name
valueStringFormatted value string
unitString (optional)Unit suffix (ms, MB, %, …)
trenddouble (optional)Positive = up (green), negative = down (red), null = hidden
descriptionString (optional)Subtitle / context line below the value
java
Stat.builder("Active Players", "142").trend(+3.0).build()
Stat.builder("Avg MSPT", "2.4").unit("ms").description("last 5 min avg").build()
ALERT

An alert callout with severity level, optional title, and message.

FieldTypeValues / Description
severitySeverityDEFAULT | INFO | SUCCESS | WARNING | ERROR
titleString (optional)Bold header line
messageStringBody text
java
Alert.of(Severity.WARNING, "High Memory", "Heap usage exceeds 85%.")
Alert.of(Severity.SUCCESS, "All systems nominal.")
CODE_BLOCK

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).

FieldTypeDescription
contentStringRaw source code string
languageStringLanguage hint shown in the header (e.g. java, yaml)
java
CodeBlock.of("max-players: 100\nonline-mode: true", "yaml")
LOG_VIEW

A scrollable log panel. Lines containing ERROR/WARN/INFO are coloured automatically.

FieldTypeDescription
entriesLogEntry[]Ordered log entries
entries[].timestamplongEpoch milliseconds
entries[].severitySeverityDEFAULT | INFO | SUCCESS | WARNING | ERROR
entries[].messageStringLog message text
java
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()
TIMELINE

An ordered list of timestamped events connected by a vertical rail.

FieldTypeDescription
eventsTimelineEvent[]Ordered list of events (oldest first)
events[].labelStringEvent title
events[].descriptionStringDetail / context text
events[].timestamplongEpoch milliseconds — formatted as local time in the UI
java
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()
SPARKLINE

A mini inline line chart with the latest value prominently displayed. Trend colour (green/red) is derived from first vs. last value.

FieldTypeDescription
labelStringMetric name
valuesdouble[]Ordered data points (oldest → newest)
unitString (optional)Unit suffix
java
new SparklineImpl("TPS (1 min buckets)", tpsHistory, null)
GAUGE

A labelled progress track that transitions from violet → amber → red as the value approaches max.

FieldTypeDescription
labelStringGauge label
currentdoubleCurrent value
maxdoubleMaximum value (min is always 0)
unitString (optional)Unit suffix
java
Gauge.of("Heap", usedMb, maxMb)
Gauge.builder("Heap", usedMb, maxMb).unit("MB").build()
LINK

A clickable hyperlink. Set external: true to open in a new tab with rel="noopener noreferrer".

FieldTypeDescription
labelStringLink display text
urlStringDestination URL (http/https URLs open in a new tab automatically)
java
Link.of("View on GitHub", "https://github.com/...")
IFRAME

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.

FieldTypeDescription
urlStringURL of the page to embed
titleString?Optional label shown above the frame
heightint?Frame height in pixels (default: 400)
java
// 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

java
// 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.

java
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

java
raisu.unregister("my:info");           // remove static category
raisu.unregisterProvider("my:stats");  // remove dynamic provider

Built-in categories

raisu-spigot defaults

Category IDIconPriorityDescription
raisu:system🖥️1OS name/version/arch, Java version/vendor/VM, server software, player count
raisu:memory💾2Heap usage progress bar, heap used/total/free/max (MB), CPU count
raisu:threads🧵3Active thread count, thread name/state/type/priority table
raisu:plugins🔌4Plugin count, name/version/enabled-status table

raisu-paper additions

Category IDIconPriorityDescription
raisu:performance📊5TPS 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.

java
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.

VersionStatusNotes
0 (absent)✓ SupportedLegacy payloads — no version field in the map
1✓ SupportedV1 components only — explicit version: 1
2✓ SupportedV2 layout (COLUMN/ROW/GRID/PANEL) + display (BADGE/STAT/ALERT/…) components
3+⚠ Best-effortUnknown 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.

GET/api/snapshot

Decodes 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

ParameterTypeRequiredDescription
codestringYesThe base64url shortcode from the viewer URL hash (everything after #)

Responses

StatusBodyWhen
200 OKapplication/json — Snapshot objectShortcode 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:

kotlin
GET /api/snapshot?code=AABB...

200 response shape:

kotlin
{
  "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.

AI Resources

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.

kotlin
// 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.

kotlin
// 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):

kotlin
// 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 IV

Component 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.

kotlin
// ── 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-400

When 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):

kotlin
{
  "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-code

The 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.

SeverityColorWhen to use
DEFAULTzinc (gray)Neutral info, no urgency
INFOblueInformational — something happened
SUCCESSemerald (green)Positive outcome, goal reached
WARNINGamberPotential problem, needs attention
ERRORredSomething 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):

java
// 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.

java
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.

java
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.

java
// 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

EnumValues
GapNONE SMALL MEDIUM LARGE
AlignmentSTART CENTER END STRETCH