Events

Register listener objects with the EventBus. Methods annotated with @EventHandler and a single Event parameter are invoked when posted.

class ExampleListener {
@EventHandler
fun onEvent(event: Event) {
// handle event
}
}

Registration:

context.eventBus.register(ExampleListener())

Routing events require the ROUTING capability. Security events require the SECURITY capability.

To stop listening, call unregister with the same listener instance.

Security Invariants

This document defines the non-negotiable security invariants for v0.4.0 and links each invariant to concrete runtime guards and tests.

Invariant 1: Backend accepts proxy-origin traffic only

  • Backend proxy-token validation is strict (v3 only, required cert claims):

    • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/security/TokenValidator.kt

  • Referral source is validated against configured proxy endpoint:

    • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/LineageBackendMod.kt

  • Unsafe downgrade is blocked by config (enforce_proxy must remain true):

    • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/config/BackendConfigLoader.kt

Tests:

  • backend-mod/src/test/kotlin/ru/hytalemodding/lineage/backend/security/TokenValidatorTest.kt

  • backend-mod/src/test/kotlin/ru/hytalemodding/lineage/backend/handshake/HandshakeInterceptorTest.kt

Invariant 2: Backend command execution cannot bypass proxy

  • Backend command mirror is populated only from validated proxy snapshots:

    • expected sender check (proxy), timestamp window check, replay check.

  • Command dispatch is gated by deterministic policy:

    • player sender required, messaging enabled, registry synchronized, non-blank command.

Runtime:

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/command/ProxyCommandBridge.kt

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/command/ProxyCommandDispatchPolicy.kt

Tests:

  • backend-mod/src/test/kotlin/ru/hytalemodding/lineage/backend/command/ProxyCommandBridgeTest.kt

  • backend-mod/src/test/kotlin/ru/hytalemodding/lineage/backend/command/ProxyCommandDispatchPolicyTest.kt

Invariant 3: Routing decision is immutable after finalization

  • RoutingDecision is one-shot by design and throws on second mutation.

Runtime:

  • api/src/main/kotlin/ru/hytalemodding/lineage/api/routing/RoutingDecision.kt

Tests:

  • proxy/src/test/kotlin/ru/hytalemodding/lineage/proxy/routing/RoutingDecisionInvariantTest.kt

  • proxy/src/test/kotlin/ru/hytalemodding/lineage/proxy/routing/EventRouterTest.kt

Invariant 4: Replay is rejected inside control window

  • Control-plane replay guard:

    • shared/src/main/kotlin/ru/hytalemodding/lineage/shared/control/ControlReplayProtector.kt

  • Backend handshake replay guard:

    • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/security/ReplayProtector.kt

Tests:

  • shared/src/test/kotlin/ru/hytalemodding/lineage/shared/control/ControlReplayProtectorTest.kt

  • backend-mod/src/test/kotlin/ru/hytalemodding/lineage/backend/security/ReplayProtectorTest.kt

Invariant 5: Security failures do not silently downgrade behavior

  • Invalid security checks follow deterministic reject paths, not fallback execution.

  • Weak/default secrets are fail-fast at config load.

  • Legacy agent mode is removed from runtime and configuration.

Primary files:

  • shared/src/main/kotlin/ru/hytalemodding/lineage/shared/security/SecretStrengthPolicy.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/config/TomlLoader.kt

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/config/BackendConfigLoader.kt

Getting started

Lineage mods are plain JVM jars. A mod is discovered by scanning for the @LineageModInfo annotation and instantiating the annotated class.

Minimal mod

Kotlin:

@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.4.0",
authors = ["YourName"]
)
class HelloMod : LineageMod() {
override fun onEnable() {
context.logger.info("Hello from Lineage!")
}
}

Java:

@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.4.0",
authors = {"YourName"}
)
public final class HelloMod extends LineageMod {
@Override
public void onEnable() {
context.getLogger().info("Hello from Lineage!");
}
}

Capabilities

Mods run without privileged capabilities by default. Declare the minimum set you need:

Kotlin:

@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.4.0",
capabilities = [ModCapability.MESSAGING, ModCapability.PLAYERS]
)
class HelloMod : LineageMod()

Java:

@LineageModInfo(
id = "hello",
name = "Hello Mod",
version = "1.0.0",
apiVersion = "0.4.0",
capabilities = {ModCapability.MESSAGING, ModCapability.PLAYERS}
)
public final class HelloMod extends LineageMod {
}

Packaging

  • Build a jar that includes your mod class and resources.

  • Place the jar into mods/ next to the proxy config.toml.

  • Per-mod data is stored under mods/<mod-id>/.

Dependency

Lineage API is published on Maven Central.

Gradle Kotlin DSL:

dependencies {
implementation("ru.hytalemodding.lineage:api:0.4.0")
}

Gradle Groovy DSL:

dependencies {
implementation "ru.hytalemodding.lineage:api:0.4.0"
}

Maven:

<dependency>
<groupId>ru.hytalemodding.lineage</groupId>
<artifactId>api</artifactId>
<version>0.4.0</version>
</dependency>

You can still build from source and depend on the local :api module when needed.

Mod metadata

Metadata is provided via @LineageModInfo on the mod main class.

Fields:

  • id: lowercase identifier, 1-32 chars, [a-z0-9_-]+

  • name: human name, 1-64 chars, [A-Za-z0-9 _.-]+

  • version: MAJOR.MINOR.PATCH

  • apiVersion: MAJOR.MINOR.PATCH

  • authors: list of author names

  • description: optional text

  • dependencies: required dependencies

  • softDependencies: optional dependencies

  • capabilities: explicit access to privileged APIs

  • website, license: optional strings

Capabilities:

  • COMMANDS

  • MESSAGING

  • PLAYERS

  • BACKENDS

  • ROUTING

  • SECURITY

  • PERMISSIONS

  • SCHEDULER

  • SERVICES

Dependency entries accept version constraints:

core
core>=1.2.0
worlds^2.0.0

If a required dependency is missing or does not satisfy the constraint, the mod will not load.

Proxy Auth, Join and Transfer Flow (v0.4.0)

This document describes the runtime flow exactly as implemented in proxy and backend-mod.

Source of truth

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/net/QuicSessionHandler.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/net/handler/ConnectPacketInterceptor.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/net/handler/StreamBridge.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/control/ControlPlaneService.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/player/PlayerTransferService.kt

  • proxy/src/main/kotlin/ru/hytalemodding/lineage/proxy/net/BackendAvailabilityTracker.kt

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/LineageBackendMod.kt

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/security/TokenValidator.kt

  • backend-mod/src/main/kotlin/ru/hytalemodding/lineage/backend/control/BackendControlPlaneService.kt

1. Initial join and authenticated handshake

sequenceDiagram
autonumber
participant C as Client
participant P as Proxy QUIC listener
participant SI as Stream0 interceptor
participant PB as Proxy->Backend QUIC
participant B as Backend server
participant BM as Backend mod

C->>P: QUIC/TLS connect
P->>P: QuicSessionHandler.channelActive()
P->>P: capture client cert (if present)
C->>SI: Stream 0 Connect packet
SI->>SI: ConnectPacketInterceptor.channelRead()
SI->>SI: decode/validate Connect + routing decision
SI->>SI: issue proxy token (client cert + proxy cert)
SI->>SI: rewrite Connect referralSource/referralData

SI->>PB: ensure backend channel (with fallback policy)
PB->>B: Open backend QUIC/TLS
SI->>B: Forward modified Connect

B->>BM: PacketAdapters inbound Connect bridge
BM->>BM: validate referral source + TokenValidator.validate()
BM->>B: apply client cert attr + server cert

B->>C: continue AUTHENTICATED flow (identity/grant/token)
B-->>P: token validation notice (control-plane)
P->>P: ControlPlaneService.handleTokenValidation()

Key implementation points:

  • Stream 0 interception and packet rewrite: ConnectPacketInterceptor.

  • Proxy token injection: TokenService.issueToken(...) from ConnectPacketInterceptor.

  • Backend cert policy and ALPN checks before stream bridge: QuicSessionHandler.connectBackend(...).

  • Final backend-side validation and cert context apply: LineageBackendMod.registerHandshakeBridge() + LineageBackendMod.onPlayerConnect().

2. Server transfer flow (/transfer)

sequenceDiagram
autonumber
participant A as Admin/Player command sender
participant P as Proxy command layer
participant TS as PlayerTransferService
participant CPS as ControlPlaneService (proxy)
participant BCM as BackendControlPlaneService
participant B as Current backend
participant C as Client

A->>P: /transfer <backend>
P->>TS: requestTransferDetailed(player, target)
TS->>TS: validate player/backend/status
TS->>CPS: sendTransferRequest(correlationId, referralData)

CPS->>BCM: CONTROL TRANSFER_REQUEST
BCM->>B: referToServer(proxyHost, proxyPort, referralData)
BCM-->>CPS: CONTROL TRANSFER_RESULT

C->>P: reconnect via proxy using referralData
P->>P: validate transfer token in Connect interceptor
P->>P: route to requested backend

Key implementation points:

  • Command entry: TransferCommand.execute(...).

  • Transfer request orchestration: PlayerTransferService.requestTransferDetailed(...).

  • Control-plane encode/send/verify: ControlPlaneService and BackendControlPlaneService.

  • Transfer token consume path: ConnectPacketInterceptor.resolveBackend(...).

3. Backend status, fallback and reconnect behavior

sequenceDiagram
autonumber
participant BCM as BackendControlPlaneService
participant CPS as ControlPlaneService (proxy)
participant BAT as BackendAvailabilityTracker
participant SI as ConnectPacketInterceptor
participant QSH as QuicSessionHandler

BCM-->>CPS: BACKEND_STATUS ONLINE/OFFLINE heartbeat
CPS->>BAT: markReportedOnline/markReportedOffline

SI->>BAT: status(selectedBackend)
alt selected backend offline
SI->>SI: pick fallback backend if available
end

QSH->>BAT: connect failure -> markUnavailable
QSH->>QSH: connectBackendWithFallback(...)

Key implementation points:

  • Backend status heartbeats and offline burst on stop: BackendControlPlaneService.start()/stop().

  • Proxy status ingestion and state update: ControlPlaneService.handleBackendStatus(...).

  • Connect-time reroute + connect-time fallback retries: ConnectPacketInterceptor.resolveBackend(...) and QuicSessionHandler.connectBackendWithFallback(...).

4. Security invariants enforced by this flow

  • Backend token validation is never bypassed; backend validates referral token and context.

  • Proxy and backend control-plane messages are envelope-validated (sender/time/ttl/nonce replay/payload limits).

  • Backend selection for join/transfer is bounded by availability tracker and deterministic fallback logic.

  • Stream bridging starts only after backend channel and handshake path are in a valid state.

title: Operations Runbook

---

Operations Runbook

This runbook defines safe operational procedures for proxy/backend deployments in v0.4.0.

Safe Defaults

Keep these defaults unless there is a documented exception:

  • Proxy:

    • security.proxy_secret: strong random value.

    • [messaging].enabled: true when command/control-plane sync is required.

    • [messaging].control_*: keep defaults unless load-testing indicates a required change.

    • [rate_limits].handshake_concurrent_max: keep bounded (256 default).

    • [rate_limits].routing_concurrent_max: keep bounded (256 default).

  • Backend:

    • enforce_proxy = true.

    • require_authenticated_mode = true.

    • control_expected_sender_id = "proxy" (or explicit trusted sender id).

    • proxy_secret rotation only with overlap window (proxy_secret_previous) and planned rollout.

Startup Sync Procedure

Use this sequence on normal startup:

  1. Start proxy with valid config and verify /health is READY or DEGRADED.

  2. Start backend-mod and verify config loads without validation errors.

  3. Verify command registry sync:

    • backend requests snapshot;

    • proxy sends snapshot;

    • backend marks registry synchronized.

  4. Validate control-plane channel with a controlled transfer command.

Expected result:

  • No VERSION_MISMATCH, UNEXPECTED_SENDER, INVALID_TIMESTAMP, or REPLAYED_* spikes in reject counters.

Failure Scenario: Messaging Unavailable

Symptoms:

  • backend command bridge is not synchronized;

  • control-plane transfer path unavailable;

  • messaging channel errors in logs.

Actions:

  1. Confirm proxy/ backend messaging bind addresses and ports.

  2. Confirm shared secret parity (proxy_secret) and sender expectations (control_sender_id, control_expected_sender_id).

  3. Keep backend running with command bridge disabled until messaging is healthy.

  4. Restore messaging, trigger snapshot sync again, then re-enable command routing.

Do not:

  • disable enforce_proxy;

  • bypass control-plane validation logic.

Failure Scenario: Registry Desync

Symptoms:

  • backend command execution rejected with unsynchronized registry policy;

  • command set mismatch between proxy and backend.

Actions:

  1. Trigger registry snapshot request from backend.

  2. Confirm proxy snapshot encode path has no payload-limit reject.

  3. Confirm snapshot sender/version/timestamp/replay checks pass on backend.

  4. If desync persists, restart backend bridge component first, then proxy messaging component.

Failure Scenario: Version Mismatch

Symptoms:

  • deterministic reject reason VERSION_MISMATCH in command/control-plane paths.

Actions:

  1. Stop rollout immediately (no partial deploy).

  2. Align proxy/backend/shared module versions.

  3. Re-run compatibility tests before re-enabling traffic.

  4. Resume rollout only after mismatch counters remain stable at zero.

Rollback Procedure

Use rollback if production safety guarantees cannot be restored quickly:

  1. Stop new deployments.

  2. Roll back proxy + backend-mod + shared artifacts as one versioned set.

  3. Keep config compatible with rolled-back artifact version.

  4. Validate:

    • auth mode requirement,

    • proxy enforcement,

    • control-plane sender/version validation,

    • health endpoint status.

Rollback acceptance:

  • login/transfer/control-plane paths are deterministic and stable under smoke load.

Post-Incident Checklist

After recovery:

  1. Save relevant structured logs with correlation ids.

  2. Export /metrics and /status snapshots for incident window.

  3. Record root cause, impacted invariants, and remediation PRs.

  4. Add or update regression tests for the exact failure mode.

Configuration

Each mod gets its own data folder under mods/<mod-id>/. Use ConfigManager to create or load TOML files in that directory.

val config = context.configManager.config(
name = "settings",
createIfMissing = true
) {
"""
enabled = true
greeting = "hello"
""".trimIndent()
}

Paths are relative to the mod folder. If name has no file extension, .toml is appended automatically.

Examples:

  • settings ->mods/<id>/settings.toml

  • nested/chat ->mods/<id>/nested/chat.toml

Lineage Modding

This documentation covers the public API used to build Lineage mods. The same Markdown files are used for Dokka and GitHub docs to keep them in sync. Some features require explicit capabilities declared in @LineageModInfo.

Contents

  • getting-started.md

  • mod-metadata.md

  • lifecycle.md

  • commands.md

  • events.md

  • players.md

  • messaging.md

  • config.md

  • permissions.md

  • scheduler.md

  • services.md

  • localization-text.md

  • backends.md

  • operations-runbook.md

  • logging-ux.md

  • security-invariants.md

  • proxy-auth-routing-flow.md

Permissions

Use PermissionChecker to evaluate permission strings against a subject.

if (context.permissionChecker.hasPermission(sender, "lineage.example.use")) {
sender.sendMessage("Allowed")
}

CommandSender implements PermissionSubject, so it can be checked directly.

Scheduler

Schedule work on the proxy runtime with Scheduler.

val handle = context.scheduler.runLater(Duration.ofSeconds(5)) {
context.logger.info("Delayed task")
}

Use runSync, runAsync, or runRepeating depending on your needs. Cancel with handle.cancel().

Messaging

Messaging provides UDP channels for proxy and backend communication.

Requires the MESSAGING capability in @LineageModInfo. Channel ids under the lineage. namespace are reserved for internal traffic.

Raw channel:

val channel = context.messaging.registerChannel("mods:hello") { message ->
val text = message.payload.toString(Charsets.UTF_8)
context.logger.info("Got: {}", text)
}
channel.send("hi".toByteArray())

Typed channel:

val typed = MessagingChannels.registerTyped(
context.messaging,
"mods:chat",
Codecs.UTF8_STRING
) { message ->
context.logger.info("Got: {}", message.payload)
}
typed.send("hello")

Lifecycle

LineageMod exposes three lifecycle hooks:

  • onLoad(context): called once after the mod is constructed and the ModContext is ready.

  • onEnable(): called after all mods are loaded and dependency order is resolved.

  • onDisable(): called during shutdown or reload.

Use onLoad for wiring services and onEnable for runtime logic.

title: Logging UX

---

Logging UX

This guide defines deterministic log triage for production operations. Goal: diagnose incidents using reason + correlationId + /metrics//status without manual free-form parsing.

Structured Event Shape

Security-critical proxy/backend logs use one stable key/value format:

  • category (handshake, transfer, control-plane, command-gateway, command-registry-sync)

  • severity (INFO, WARN, ERROR)

  • reason (fixed reject/result reason)

  • correlationId (when available)

  • extra fields (sorted by key)

Example:

category=control-plane severity=WARN reason=INVALID_TIMESTAMP correlationId=proxy:nonce details=timestamp_window_validation_failed

Correlation Rules

Use this priority order:

  1. correlationId (primary key).

  2. reason + short timestamp window.

  3. playerId/session.id for handshake and transfer incidents.

Correlation sources:

  • handshake: session.id or playerId;

  • transfer: TransferRequest.correlationId;

  • control-plane: message correlationId or fallback senderId:nonce.

Fast Triage Workflow

  1. Check health:

curl -s http://127.0.0.1:9091/health
  1. Find reject spikes by reason:

curl -s http://127.0.0.1:9091/metrics | rg "lineage_proxy_(handshake_errors|control_reject|routing_decisions)_total"
  1. Confirm runtime state:

curl -s http://127.0.0.1:9091/status
  1. Trace the top reason in logs:

rg "reason=INVALID_TIMESTAMP|reason=UNEXPECTED_SENDER" proxy.log backend.log
  1. Trace one correlationId across both sides:

rg "correlationId=proxy:1707433539123" proxy.log backend.log

Reason-to-Action Mapping

  • ALPN_MISMATCH: client/proxy protocol mismatch. Validate client ALPN and server baseline.

  • CONNECTION_RATE_LIMIT: source exceeds connection budget. Confirm flood and tune rate limits only after load test.

  • HANDSHAKE_INFLIGHT_LIMIT: handshake concurrency cap reached. Scale instances or increase cap in controlled steps.

  • INITIAL_ROUTE_DENIED: routing strategy rejected backend selection. Check backend availability and route policy.

  • INVALID_TIMESTAMP: clock skew or stale control message. Verify NTP and replay/skew windows.

  • UNEXPECTED_SENDER: control message sender id mismatch. Check control_sender_id and control_expected_sender_id.

  • PROXY_TOKEN_REJECTED: token invalid/signature mismatch/replay. Verify secret parity and rollout order.

  • VERSION_MISMATCH: mixed artifact versions. Stop partial rollout and align proxy/backend-mod/shared.

  • MALFORMED_SNAPSHOT or REPLAYED_SNAPSHOT: registry sync payload integrity/replay failure. Re-run snapshot sync path.

  • TRANSFER_FORWARD_FAILED: transfer request/result delivery issue. Verify messaging channel health and destination backend id.

No-Secret Logging Guard

Expected behavior:

  • any key containing token or secret is logged as <redacted>;

  • raw token payloads are not emitted.

Quick verification:

rg -n "token=|secret=" proxy.log backend.log | rg -v "<redacted>"

If a real secret/token appears in logs:

  1. treat it as a security incident;

  2. rotate affected secrets immediately;

  3. add a regression test for the exact log path before next rollout.

Services

ServiceRegistry lets mods share instances with each other.

val key = ServiceKey(MyService::class.java)
context.serviceRegistry.register(key, MyService())

Retrieve later:

val service = context.serviceRegistry.get(key)

Built-in services

Lineage also publishes built-in services for mods:

  • LocalizationService.SERVICE_KEY

  • TextRendererService.SERVICE_KEY

  • RoutingStrategy.SERVICE_KEY

Example:

val i18n = context.serviceRegistry.get(LocalizationService.SERVICE_KEY)
val text = context.serviceRegistry.get(TextRendererService.SERVICE_KEY)

val line = i18n?.render(player, "help_header", mapOf("count" to "3"))
val styled = text?.renderForPlayer(player, "<gradient:#ff0000:#00ffcc>Hello</gradient>")

Backends

BackendRegistry exposes the configured backend servers.

for (backend in context.backends.all()) {
context.logger.info("Backend {} -> {}:{}", backend.id, backend.host, backend.port)
}

You can move a player to a backend by id:

player.transferTo("hub-1")

Commands

Commands are registered through CommandRegistry.

class PingCommand : Command {
override val name = "ping"
override val aliases = listOf("pong")
override val description = "Basic connectivity test."
override val usage = "ping"
override val permission: String? = null
override val flags = emptySet<CommandFlag>()

override fun execute(context: CommandContext) {
context.sender.sendMessage("pong")
}

override fun suggest(context: CommandContext): List<String> = emptyList()
}

Register from your mod:

context.commandRegistry.register(PingCommand())

Use CommandContext.hasPermission(...) if you want to handle permission checks manually. CommandSender.type is CONSOLE, PLAYER, or SYSTEM. Permissions are enforced by the proxy. Backend registration is a thin bridge.

Proxy commands are mirrored to backends as native commands:

  • /<namespace>:<command> is always registered.

  • /<command> is registered only if the name is not already taken.

The namespace for core commands is lineage. For mod commands it is the mod id. If a conflict exists, only the namespaced command is available.

Supported flags:

  • PLAYER_ONLY blocks non-player senders.

  • HIDDEN skips registering the non-namespaced form.

Players

PlayerManager exposes online proxy sessions. Each ProxyPlayer represents a connected player and can be moved between backends.

val player = context.players.getByName("Example")
player?.sendMessage("Hello from the proxy")

Useful fields:

  • ProxyPlayer.id

  • ProxyPlayer.username

  • ProxyPlayer.state

  • ProxyPlayer.backendId

Transfer example:

player?.transferTo("hub-1")

Localization and Text

Lineage proxy supports file-based localization and bounded markup rendering for player/system messages.

Message bundles

  • messages/en-us.toml

  • messages/ru-ru.toml

Bundles are created automatically on first start and can be edited live.

Language fallback chain:

  1. exact locale (for example ru-ru);

  2. language family (ru ->ru-ru, en ->en-us);

  3. default en-us.

Markup renderer

Renderer profiles:

  • game

  • console

  • plain

Supported syntax:

  • &a, &l, &r (legacy + section equivalents)

  • <#RRGGBB>, &#RRGGBB

  • <red>...</red>, <bold>...</bold>, <italic>...</italic>, <underline>...</underline>

  • <gradient:#ff0000:#00ffcc>text</gradient>

Runtime limits

styles/rendering.toml controls hard limits:

  • max_input_length

  • max_nesting_depth

  • max_gradient_chars

  • max_tag_length

These bounds are enforced to keep rendering deterministic and safe under malformed input.

Mod API services

Use ServiceRegistry:

  • LocalizationService:

    • text(language, key, vars)

    • render(player, key, vars)

    • send(player, key, vars)

  • TextRendererService:

    • renderForPlayer(player, rawMarkup)

    • renderForConsole(rawMarkup)

    • renderPlain(rawMarkup)

Reload

Use messages reload from proxy console (or with permission in-game) to reload:

  • messages/*.toml

  • styles/rendering.toml

All modules:

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
Link copied to clipboard