diff --git a/docs/DESIGN-SYSTEM.md b/docs/DESIGN-SYSTEM.md
index 1e98336..406de81 100644
--- a/docs/DESIGN-SYSTEM.md
+++ b/docs/DESIGN-SYSTEM.md
@@ -174,7 +174,7 @@ Garis progres 3px (`#progress`) saat navigasi boosted >150 ms, hormati reduced-m
### 7.1 Navigasi boosted (S9)
- `
`. Setiap halaman kerja membungkus **seluruh** isi (konten, sheet, ``, skrip inline) dalam satu ``; header, tab bar, dan head tetap. `login.html` tidak memuat htmx; form logout `hx-boost="false"`.
- Elemen htmx lain di dalam halaman (stats card, segmented) wajib `hx-target` sendiri dan `hx-select="unset"` agar tidak mewarisi `#main`.
-- Skrip halaman **idempoten**: dipanggil pada `DOMContentLoaded` dan `htmx:afterSettle`, dengan guard `data-init` (lihat `list.js`, `formatTimes` di `app.js`). Listener ke `document`/`window` dari skrip inline dilarang (menumpuk setiap swap): pasang ke `#main` atau elemen di dalamnya. Sumber daya hidup (WebSocket, xterm) mendaftar fungsi pembersih ke `window.dockifyCleanups`; `app.js` memanggilnya pada `htmx:beforeSwap` target `#main`.
+- Skrip halaman **idempoten**: dipanggil pada `DOMContentLoaded` dan `htmx:afterSettle`, dengan guard `data-init` (lihat `list.js`, `formatTimes` di `app.js`). Listener ke `document`/`window` dari skrip inline dilarang (menumpuk setiap swap): pasang ke `#main` atau elemen di dalamnya. Sumber daya hidup (WebSocket, xterm) mendaftar fungsi pembersih ke `window.dockifyCleanups`; `app.js` memanggilnya pada `htmx:beforeSwap` target `#main`. `charts.js` dan `console.js` dimuat `defer`: skrip inline yang memanggilnya saat parse dibungkus `dockifyReady(fn)` (app.js; menunggu DOMContentLoaded pada load penuh, langsung jalan setelah swap). Pembersih yang dipasang saat aksi user (Console) didaftarkan saat aksi itu, bukan sekali saat skrip dimuat.
- Form dengan `onsubmit` yang bisa membatalkan harus memvalidasi lagi di `htmx:beforeRequest` (htmx tetap mengirim saat `onsubmit` mengembalikan false).
- Respons tanpa `#main` (redirect ke login) jatuh ke load penuh; 4xx/5xx boosted tetap di-swap (halaman error).
- Preload: atribut `preload="mousedown"` (touchstart otomatis) pada nav dan tautan baris; server menjawab permintaan `HX-Preloaded` dengan `Cache-Control: private, max-age=5`.
diff --git a/internal/http/static/app.js b/internal/http/static/app.js
index f91f8b4..8031a04 100644
--- a/internal/http/static/app.js
+++ b/internal/http/static/app.js
@@ -73,6 +73,12 @@ document.addEventListener('click', function (e) {
else fallback();
});
+// Inline page scripts that need a deferred asset (charts.js, console.js): on a full load they run before it, so wait for DCL.
+function dockifyReady(fn) {
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn, { once: true });
+ else fn();
+}
+
// xterm is only needed for the SSH console: load it on first use.
var xtermLoading = null;
function loadXterm(cb) {
diff --git a/internal/http/static/console.js b/internal/http/static/console.js
index 8aeba4b..e9bd1e7 100644
--- a/internal/http/static/console.js
+++ b/internal/http/static/console.js
@@ -4,6 +4,7 @@ function dockifyConsole(cfg) {
function el(id) { return document.getElementById(id); }
function setStatus(text, cls) {
var s = el(cfg.status);
+ if (!s) return; // socket closed after the page was swapped out
s.textContent = text;
s.className = 'badge ' + cls;
}
@@ -47,6 +48,7 @@ function dockifyConsole(cfg) {
if (box.hidden) {
box.hidden = false;
if (cfg.openLabel) btn.textContent = cfg.openLabel;
+ (window.dockifyCleanups = window.dockifyCleanups || []).push(closeConsoles);
loadXterm(connect);
} else {
box.hidden = true;
@@ -113,7 +115,8 @@ function toggleAppConsole() {
appConsole.toggle();
}
// A boosted navigation replaces #main; close any open console so its socket does not leak.
-(window.dockifyCleanups = window.dockifyCleanups || []).push(function () {
+// Registered on open: the cleanup list is emptied on every swap.
+function closeConsoles() {
if (serverConsole) { serverConsole.disconnect(); serverConsole = null; }
if (appConsole) { appConsole.disconnect(); appConsole = null; }
-});
+}
diff --git a/internal/http/templates/apps_detail.html b/internal/http/templates/apps_detail.html
index 0a76f51..4d8800d 100644
--- a/internal/http/templates/apps_detail.html
+++ b/internal/http/templates/apps_detail.html
@@ -91,7 +91,7 @@
Resource usage
-
-
+
+
{{end}}
{{define "nav"}}
diff --git a/internal/http/templates/servers_detail.html b/internal/http/templates/servers_detail.html
index 7c7c66b..1cd20f1 100644
--- a/internal/http/templates/servers_detail.html
+++ b/internal/http/templates/servers_detail.html
@@ -189,7 +189,7 @@ Danger zone
{{if eq .Server.Status "online"}}
{{end}}