diff --git a/docs/01_Overview.md b/docs/01_Overview.md
index 2dfd8df..a7496e2 100644
--- a/docs/01_Overview.md
+++ b/docs/01_Overview.md
@@ -45,6 +45,7 @@ External dependencies:
| [listener/BanEnforcer.java](../src/main/java/net/kewwbec/staff/listener/BanEnforcer.java) | `PlayerSetupConnectEvent` -> cancel + set reason if banned. |
| [listener/ChatEnforcer.java](../src/main/java/net/kewwbec/staff/listener/ChatEnforcer.java) | `PlayerChatEvent` -> cancel if muted; otherwise route to staff chat if toggle is on. |
| [listener/PlayerSeenTracker.java](../src/main/java/net/kewwbec/staff/listener/PlayerSeenTracker.java) | Connect/disconnect -> update players_seen, load/clear mute cache, clear staff-chat toggle. |
+| [listener/StaffPresenceListener.java](../src/main/java/net/kewwbec/staff/listener/StaffPresenceListener.java) | Connect/disconnect of staff -> announce on staff chat when they actually join/leave the **network** (suppresses server transfers via NetworkCore PlayerPresence + a configurable delay). |
| [command/punish/PunishmentCommands.java](../src/main/java/net/kewwbec/staff/command/punish/PunishmentCommands.java) | All 8 punishment commands as nested classes. |
| [command/chat/StaffChatCommand.java](../src/main/java/net/kewwbec/staff/command/chat/StaffChatCommand.java) | `/sc` - toggles staff-chat-mode for the caller. |
| [command/lookup/LookupCommand.java](../src/main/java/net/kewwbec/staff/command/lookup/LookupCommand.java), [HistoryCommand.java](../src/main/java/net/kewwbec/staff/command/lookup/HistoryCommand.java) | `/lookup`, `/history`. |
diff --git a/docs/02_Configuration.md b/docs/02_Configuration.md
index 57133f2..5fbf719 100644
--- a/docs/02_Configuration.md
+++ b/docs/02_Configuration.md
@@ -15,6 +15,10 @@
"tables": {
"punishments": "staff_punishments",
"players_seen": "staff_players_seen"
+ },
+ "presence": {
+ "announce_staff_join_leave": true,
+ "leave_suppression_seconds": 10
}
}
```
@@ -37,6 +41,15 @@ Permission gating no longer lives here. Each command checks a permission node (`
| `punishments` | `"staff_punishments"` | Table name. Change only if you really need to and you're prepared to migrate data. |
| `players_seen` | `"staff_players_seen"` | Same. |
+### presence
+
+| Field | Default | Meaning |
+|---|---|---|
+| `announce_staff_join_leave` | `true` | When a staff member (anyone with `networkstaff.chat`) joins or leaves the **network**, announce on the staff chat channel. Server-to-server transfers via `/send` or `/hub` are suppressed. |
+| `leave_suppression_seconds` | `10` | After a disconnect, wait this many seconds before declaring the player has "left the network." If they reconnect anywhere (any server) in that window, no leave message is sent. Tune higher if your transfers take longer than 10s; tune lower for snappier announcements at the cost of false positives. |
+
+Requires NetworkCore's `PlayerPresence` service. If NetworkCore isn't running or PlayerPresence is unavailable, presence announcements are silently skipped (with a warning in the boot log).
+
## Setting up staff permissions
1. In your Hytale server's permission config, create a `staff` group (or whatever you want to call it).
diff --git a/docs/07_UI.md b/docs/07_UI.md
index 9db7d38..4ee641d 100644
--- a/docs/07_UI.md
+++ b/docs/07_UI.md
@@ -102,7 +102,7 @@ This is simpler than HyUI's `updatePage(true)` re-render dance and keeps each pa
- `
` - root, dims background, captures input
- `
` - main framed window with header
- `
` - inner content area
-- `` - clickable button (use `class="back-button"` for back-themed)
+- `` - clickable button. **Don't use `class="back-button"`** with inline HTML - that class triggers HyUI's `BackButton.ui` template which expects a nested `#HyUIButton` child that inline buttons don't have, and your `Activating` listener fails to bind with "Target element... has no compatible Activating event". Plain `
diff --git a/src/main/java/net/kewwbec/staff/ui/PageLayout.java b/src/main/java/net/kewwbec/staff/ui/PageLayout.java
new file mode 100644
index 0000000..0475217
--- /dev/null
+++ b/src/main/java/net/kewwbec/staff/ui/PageLayout.java
@@ -0,0 +1,40 @@
+package net.kewwbec.staff.ui;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Standard outer wrapper for every Staff HyUI page: 800x600 decorated container, scrolling contents.
+ *
+ * Use {@link #wrap(String, String)} to produce the full HTML; {@link #escape(String)} for user data.
+ */
+public final class PageLayout {
+
+ public static final int DEFAULT_WIDTH = 800;
+ public static final int DEFAULT_HEIGHT = 600;
+
+ private PageLayout() {
+ }
+
+ @Nonnull
+ public static String wrap(@Nonnull String title, @Nonnull String contentsHtml) {
+ return wrap(title, contentsHtml, DEFAULT_WIDTH, DEFAULT_HEIGHT);
+ }
+
+ @Nonnull
+ public static String wrap(@Nonnull String title, @Nonnull String contentsHtml, int width, int height) {
+ return """
+