@enigmax/dashboard 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/assets/index.html +53 -20
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -13,7 +13,7 @@
13
13
  body {
14
14
  margin: 0; background: var(--bg); color: var(--text);
15
15
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
16
- -webkit-font-smoothing: antialiased; padding: 24px; max-width: 1040px; margin: 0 auto;
16
+ -webkit-font-smoothing: antialiased; padding: 24px; max-width: 2200px; margin: 0 auto;
17
17
  }
18
18
  a { color: var(--accent2); }
19
19
  header { display: flex; align-items: center; gap: 20px; margin-bottom: 24px; flex-wrap: wrap; }
@@ -108,12 +108,18 @@
108
108
  border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: inherit; cursor: pointer;
109
109
  }
110
110
  .set-note { color: var(--accent); font-size: 12px; min-height: 16px; margin-top: 10px; }
111
+ /* Settings: two-column grid of rows on wide screens, so a long config list stays compact. */
112
+ .set-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 0 32px; }
113
+ .set-grid .set-row { padding: 10px 0; }
114
+ .panel > .set-cat-h { margin-top: 0; }
111
115
  .tag {
112
116
  display: inline-block; font-size: 11px; color: var(--muted); background: var(--surface2);
113
117
  border: 1px solid var(--border); border-radius: 6px; padding: 1px 7px; margin-left: 6px; vertical-align: middle;
114
118
  }
115
119
  .tag.ext { color: var(--accent2); }
116
120
  .tag.warn { color: var(--accent); border-color: var(--accent); }
121
+ .tag.exp { color: var(--accent); border-color: var(--accent); background: rgba(224,164,88,0.10); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
122
+ .tag.meas { color: var(--good); border-color: var(--good); background: rgba(111,207,151,0.10); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
117
123
  .editor {
118
124
  width: 100%; min-height: 360px; resize: vertical; background: var(--surface2); color: var(--text);
119
125
  border: 1px solid var(--border); border-radius: 8px; padding: 12px;
@@ -175,7 +181,10 @@
175
181
  <div class="panel">
176
182
  <div class="panel-head">
177
183
  <h2>Enigma Systems <small>what's active - configuration, not savings</small></h2>
178
- <div class="ctrls"><button id="updateBtn" type="button" class="toggle on">Check &amp; update</button></div>
184
+ <div class="ctrls">
185
+ <button id="refreshBtn" type="button" class="toggle">Refresh</button>
186
+ <button id="updateBtn" type="button" class="toggle on">Check &amp; update</button>
187
+ </div>
179
188
  </div>
180
189
  <div class="sub" style="margin-bottom:14px">Only Context Compression and (opt-in) real tool-usage are measured for savings below. The rest is shown as state: enigma can't measure their effect because they run inside the agent, not through enigma - so it reports their configuration honestly instead of inventing a number.</div>
181
190
  <div id="systems" class="sys-grid"><div class="empty" style="padding:16px 0">Loading...</div></div>
@@ -293,11 +302,9 @@
293
302
  <div class="page-head">
294
303
  <h1>Settings</h1>
295
304
  <p>Everything you can configure with <code>enigma config</code> or the terminal UI, editable here. Changes apply at global scope and take effect immediately; toggles that change agent memory need an agent restart.</p>
296
- </div>
297
- <div class="panel">
298
- <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
299
305
  <div id="settingsNote" class="set-note"></div>
300
306
  </div>
307
+ <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
301
308
  </section>
302
309
 
303
310
  <footer>
@@ -644,9 +651,14 @@
644
651
  $("updated").textContent = "Updated " + new Date(data.generatedAt || Date.now()).toLocaleTimeString();
645
652
  }
646
653
 
647
- async function poll() {
648
- // Headroom pattern: never hit the server while the tab is hidden.
654
+ // Auto-refresh is on by default while the tab is focused; `live` mirrors the
655
+ // dashboard-live config (read from /api/status). A forced refresh (button / view
656
+ // change) always runs so the user can refresh even with auto-refresh off.
657
+ let live = true;
658
+ async function poll(force) {
659
+ // Never hit the server while the tab is hidden, or when auto-refresh is off (unless forced).
649
660
  if (document.hidden) return;
661
+ if (!live && !force) return;
650
662
  try {
651
663
  const res = await fetch("/api/stats", { cache: "no-store" });
652
664
  render(await res.json());
@@ -659,6 +671,15 @@
659
671
  // --- settings subpage (mirrors the TUI registry over /api/settings) ---
660
672
  function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c])); }
661
673
 
674
+ // Turn "(experimental)" and " · measured/experimental" label suffixes into visual badges.
675
+ function labelWithBadges(text) {
676
+ let name = String(text);
677
+ const badges = [];
678
+ if (/\(experimental\)/i.test(name)) { name = name.replace(/\s*\(experimental\)/i, ""); badges.push(["experimental", "exp"]); }
679
+ name = name.replace(/\s*·\s*(measured|experimental)\b/gi, (_, w) => { badges.push([w.toLowerCase(), w.toLowerCase() === "experimental" ? "exp" : "meas"]); return ""; });
680
+ return esc(name) + badges.map(([t, c]) => ' <span class="tag ' + c + '">' + esc(t) + "</span>").join("");
681
+ }
682
+
662
683
  function settingRow(s) {
663
684
  const ctl = s.choices
664
685
  ? '<select class="choice" data-skey="' + esc(s.key) + '">'
@@ -666,7 +687,7 @@
666
687
  + "</select>"
667
688
  : '<button type="button" class="toggle' + (s.value ? " on" : "") + '" data-skey="' + esc(s.key)
668
689
  + '" data-kind="bool" data-on="' + (s.value ? 1 : 0) + '">' + (s.value ? "On" : "Off") + "</button>";
669
- return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.label) + "</div>"
690
+ return '<div class="set-row"><div class="set-meta"><div class="set-name">' + labelWithBadges(s.label) + "</div>"
670
691
  + '<div class="set-hint">' + esc(s.hint) + (s.globalOnly ? " · global" : "") + "</div></div>"
671
692
  + '<div class="set-ctl">' + ctl + "</div></div>";
672
693
  }
@@ -674,9 +695,11 @@
674
695
  function renderSettings(cats) {
675
696
  const el = $("settingsBody");
676
697
  if (!cats.length) { el.innerHTML = '<div class="empty" style="padding:24px 0">No settings available.</div>'; return; }
677
- el.innerHTML = cats.map((c) => '<div class="set-cat"><div class="set-cat-h">' + esc(c.title) + "</div>"
698
+ // One panel per category; settings laid out in a responsive 2-column grid so a
699
+ // long config list stays scannable on a wide screen.
700
+ el.innerHTML = cats.map((c) => '<div class="panel"><div class="set-cat-h">' + esc(c.title) + "</div>"
678
701
  + (c.blurb ? '<div class="set-cat-b">' + esc(c.blurb) + "</div>" : "")
679
- + c.settings.map(settingRow).join("") + "</div>").join("");
702
+ + '<div class="set-grid">' + c.settings.map(settingRow).join("") + "</div></div>").join("");
680
703
  }
681
704
 
682
705
  async function postSetting(key, value, ctl) {
@@ -720,7 +743,7 @@
720
743
  function renderSystems(s) {
721
744
  const el = $("systems");
722
745
  if (!s) { el.innerHTML = '<div class="empty" style="padding:16px 0">Status unavailable.</div>'; return; }
723
- const item = (k, v) => '<div class="sys-item"><span class="k">' + k + '</span><span class="v">' + v + "</span></div>";
746
+ const item = (k, v) => '<div class="sys-item"><span class="k">' + labelWithBadges(k) + '</span><span class="v">' + v + "</span></div>";
724
747
  const sk = s.skills || {};
725
748
  const skillTags = '<span class="tag">' + (sk.enigma || 0) + " enigma</span>"
726
749
  + '<span class="tag ext">' + (sk.external || 0) + " external</span>"
@@ -753,19 +776,28 @@
753
776
  try {
754
777
  const res = await fetch("/api/update", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
755
778
  const out = await res.json();
756
- $("updateNote").textContent = out.ok ? (out.note || "Updated.") : ("Update failed: " + (out.error || "error"));
757
- loadSystems();
758
- if (skillsLoaded) loadSkills();
779
+ if (!out.ok) { $("updateNote").textContent = "Update failed: " + (out.error || "error"); return; }
780
+ // The UI bundle may have been refreshed server-side; reload so the browser
781
+ // picks up the new dashboard and fresh data automatically.
782
+ $("updateNote").textContent = (out.note || "Updated.") + " Refreshing the dashboard...";
783
+ setTimeout(() => location.reload(), 1200);
759
784
  } catch { $("updateNote").textContent = "Could not reach the server to update."; }
760
785
  }
761
786
 
762
- async function loadSystems() {
787
+ async function loadSystems(force) {
788
+ if (!live && !force) return;
763
789
  try {
764
790
  const r = await fetch("/api/status", { cache: "no-store" });
765
- renderSystems((await r.json()).systems);
791
+ const sys = (await r.json()).systems;
792
+ if (sys) live = sys.live !== false; // pick up the dashboard-live config
793
+ renderSystems(sys);
766
794
  } catch { $("systems").innerHTML = '<div class="empty" style="padding:16px 0">Status unavailable.</div>'; }
767
795
  }
768
796
 
797
+ function onSavings() { const h = (location.hash || "").replace(/^#\/?/, ""); return h !== "settings" && h !== "skills"; }
798
+ // One refresh of everything visible on the Savings view; `force` ignores auto-refresh-off.
799
+ function refreshAll(force) { poll(force); if (onSavings()) loadSystems(force); }
800
+
769
801
  // --- skills subpage (lists enigma + external skills over /api/skills) ---
770
802
  function skillRow(s) {
771
803
  const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
@@ -871,16 +903,17 @@
871
903
  if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
872
904
  if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
873
905
  // The chart was sized while its view may have been hidden; nudge it on return.
874
- if (v === "savings") { loadSystems(); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
906
+ if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
875
907
  }
876
908
 
877
909
  initChart();
878
- poll();
879
- setInterval(poll, POLL_MS);
880
- document.addEventListener("visibilitychange", () => { if (!document.hidden) poll(); });
910
+ poll(true);
911
+ setInterval(() => refreshAll(false), POLL_MS);
912
+ document.addEventListener("visibilitychange", () => { if (!document.hidden) refreshAll(true); });
881
913
  wireSettings();
882
914
  wireSkills();
883
915
  $("updateBtn").addEventListener("click", runUpdate);
916
+ $("refreshBtn").addEventListener("click", () => refreshAll(true));
884
917
  window.addEventListener("hashchange", route);
885
918
  route();
886
919
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
5
5
  "type": "module",
6
6
  "files": [