@enigmax/dashboard 0.1.0 → 0.1.1

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 (3) hide show
  1. package/README.md +8 -14
  2. package/assets/index.html +136 -102
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,22 +1,16 @@
1
1
  # @enigmax/dashboard
2
2
 
3
3
  The browser UI for [enigma](https://github.com/FJRG2007/enigma)'s local savings
4
- dashboard: a single static page (`assets/index.html`) plus a vendored charting library
5
- (`assets/lib/chart.min.js`).
4
+ dashboard: a single static page plus a vendored charting library.
6
5
 
7
- You do not install this directly. enigma-cli fetches it **on demand** the first time you
8
- open the dashboard (`enigma dashboard`) or enable it (`enigma config dashboard on`), into
9
- a managed directory under `~/.enigma/dashboard`, and keeps it up to date on
10
- `enigma update`. enigma's loopback HTTP server serves these files and provides the
11
- `/api/stats` and `/api/settings` endpoints the page talks to.
6
+ You do not install this directly. enigma fetches it on demand the first time you open the
7
+ dashboard (`enigma dashboard`) or enable it (`enigma config dashboard on`), and keeps it up
8
+ to date on `enigma update`.
12
9
 
13
- Keeping the UI out of the base `enigma-cli` package means users who never open the
14
- dashboard never download it (the chart library alone is ~196 KB).
15
-
16
- The page is served only on `127.0.0.1` (never network-facing) and the settings write
17
- endpoint is origin-guarded by enigma. The chart library retains its upstream Apache-2.0
18
- license header as required; its attribution logo is suppressed in enigma's own CSS.
10
+ Shipping the UI separately keeps it out of the base `enigma-cli` package, so people who
11
+ never open the dashboard never download it. The dashboard runs only on your own machine
12
+ (`127.0.0.1`); it is never exposed to the network.
19
13
 
20
14
  ## License
21
15
 
22
- Apache-2.0
16
+ Apache-2.0. The bundled charting library retains its upstream Apache-2.0 license header.
package/assets/index.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Enigma - Context Savings</title>
6
+ <title>Enigma - Dashboard</title>
7
7
  <style>
8
8
  :root {
9
9
  --bg: #0b0e14; --surface: #151a23; --surface2: #1b2230; --border: #232a36;
@@ -13,13 +13,21 @@
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: 1000px; margin: 0 auto;
16
+ -webkit-font-smoothing: antialiased; padding: 24px; max-width: 1040px; margin: 0 auto;
17
17
  }
18
18
  a { color: var(--accent2); }
19
- header { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; }
19
+ header { display: flex; align-items: center; gap: 20px; margin-bottom: 24px; flex-wrap: wrap; }
20
+ .brand { display: flex; flex-direction: column; }
20
21
  .wordmark { font-size: 20px; font-weight: 700; letter-spacing: 0.5px; }
21
22
  .wordmark span { color: var(--accent); }
22
23
  .sub { color: var(--muted); font-size: 13px; }
24
+ .tabs { display: flex; gap: 4px; }
25
+ .tabs .tab {
26
+ text-decoration: none; color: var(--muted); font-size: 13px; font-weight: 600;
27
+ padding: 7px 16px; border-radius: 8px; border: 1px solid transparent; transition: background .15s, color .15s, border-color .15s;
28
+ }
29
+ .tabs .tab:hover { color: var(--text); background: var(--surface); }
30
+ .tabs .tab.active { color: var(--accent); background: var(--surface); border-color: var(--border); }
23
31
  .live { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
24
32
  .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 8px var(--good); }
25
33
  .dot.stale { background: var(--muted); box-shadow: none; }
@@ -76,6 +84,9 @@
76
84
  .stat-line b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
77
85
  footer { color: var(--muted); font-size: 12px; line-height: 1.6; margin-top: 8px; }
78
86
  code { background: var(--surface2); padding: 1px 6px; border-radius: 5px; color: var(--accent2); }
87
+ .page-head { margin-bottom: 18px; }
88
+ .page-head h1 { font-size: 18px; font-weight: 700; margin: 0 0 4px; }
89
+ .page-head p { color: var(--muted); font-size: 13px; margin: 0; max-width: 720px; line-height: 1.5; }
79
90
  .set-cat { margin-bottom: 18px; }
80
91
  .set-cat:last-child { margin-bottom: 0; }
81
92
  .set-cat-h { color: var(--accent); font-size: 12px; text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 4px; }
@@ -97,15 +108,19 @@
97
108
  border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: inherit; cursor: pointer;
98
109
  }
99
110
  .set-note { color: var(--accent); font-size: 12px; min-height: 16px; margin-top: 10px; }
100
- @media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } }
111
+ @media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } header { gap: 12px; } }
101
112
  </style>
102
113
  </head>
103
114
  <body>
104
115
  <header>
105
- <div>
116
+ <div class="brand">
106
117
  <div class="wordmark">Enigma<span>.</span></div>
107
- <div class="sub">Context Compression Savings</div>
118
+ <div class="sub">Savings dashboard</div>
108
119
  </div>
120
+ <nav class="tabs">
121
+ <a href="#/" data-view="savings" class="tab active">Savings</a>
122
+ <a href="#/settings" data-view="settings" class="tab">Settings</a>
123
+ </nav>
109
124
  <div class="nav">
110
125
  <span class="live"><span id="dot" class="dot stale"></span><span id="updated">Connecting...</span></span>
111
126
  <nav class="links">
@@ -116,112 +131,118 @@
116
131
  <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286ZM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189Zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9461 2.4189-2.1568 2.4189Z"/></svg>
117
132
  </a>
118
133
  <a href="https://ko-fi.com/fjrg2007" target="_blank" rel="noopener noreferrer" title="Support on Ko-fi" aria-label="Support on Ko-fi">
119
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" y1="2" x2="6" y2="4"/><line x1="10" y1="2" x2="10" y2="4"/><line x1="14" y1="2" x2="14" y2="4"/></svg>
134
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" y1="2" x2="6" y2="4"/><line x1="14" y1="2" x2="14" y2="4"/></svg>
120
135
  </a>
121
136
  </nav>
122
137
  </div>
123
138
  </header>
124
139
 
125
- <div class="grid">
126
- <div class="card"><div class="label">Money Saved</div><div id="money" class="value good">-</div><div id="moneyFoot" class="foot">Estimated, by source</div></div>
127
- <div class="card"><div class="label">Time Saved</div><div id="time" class="value accent">-</div><div id="timeFoot" class="foot">Estimated prefill time</div></div>
128
- <div class="card"><div class="label">Tokens Saved</div><div id="saved" class="value">-</div><div id="savedFoot" class="foot">Across all compressions</div></div>
129
- <div class="card"><div class="label">Savings Rate</div><div id="rate" class="value accent">-</div><div class="foot">Of input tokens removed</div></div>
130
- <div class="card"><div class="label">Compressions</div><div id="calls" class="value">-</div><div class="foot">Reversible via CCR</div></div>
131
- <div class="card"><div class="label">Avg / Compression</div><div id="avg" class="value">-</div><div class="foot">Tokens saved per call</div></div>
132
- <div class="card"><div class="label">Best Compression</div><div id="best" class="value">-</div><div class="foot">Most saved in one call</div></div>
133
- </div>
134
-
135
- <div class="panel">
136
- <div class="panel-head">
137
- <h2>Savings Per Day <small id="rangeLabel"></small></h2>
138
- <div class="ctrls">
139
- <div class="ranges" id="metricToggle">
140
- <button type="button" data-m="tokens" class="active">Tokens</button>
141
- <button type="button" data-m="usd">$</button>
142
- </div>
143
- <div class="ranges" id="modeToggle">
144
- <button type="button" data-o="daily" class="active">Daily</button>
145
- <button type="button" data-o="cumulative">Cumulative</button>
146
- </div>
147
- <div class="ranges" id="ranges">
148
- <button type="button" data-d="7">7D</button>
149
- <button type="button" data-d="30" class="active">30D</button>
150
- <button type="button" data-d="90">90D</button>
151
- <button type="button" data-d="0">All</button>
140
+ <main id="view-savings">
141
+ <div class="grid">
142
+ <div class="card"><div class="label">Money Saved</div><div id="money" class="value good">-</div><div id="moneyFoot" class="foot">Estimated, by source</div></div>
143
+ <div class="card"><div class="label">Time Saved</div><div id="time" class="value accent">-</div><div id="timeFoot" class="foot">Estimated prefill time</div></div>
144
+ <div class="card"><div class="label">Tokens Saved</div><div id="saved" class="value">-</div><div id="savedFoot" class="foot">Across all compressions</div></div>
145
+ <div class="card"><div class="label">Savings Rate</div><div id="rate" class="value accent">-</div><div class="foot">Of input tokens removed</div></div>
146
+ <div class="card"><div class="label">Compressions</div><div id="calls" class="value">-</div><div class="foot">Reversible via CCR</div></div>
147
+ <div class="card"><div class="label">Avg / Compression</div><div id="avg" class="value">-</div><div class="foot">Tokens saved per call</div></div>
148
+ <div class="card"><div class="label">Best Compression</div><div id="best" class="value">-</div><div class="foot">Most saved in one call</div></div>
149
+ </div>
150
+
151
+ <div class="panel">
152
+ <div class="panel-head">
153
+ <h2>Savings Per Day <small id="rangeLabel"></small></h2>
154
+ <div class="ctrls">
155
+ <div class="ranges" id="metricToggle">
156
+ <button type="button" data-m="tokens" class="active">Tokens</button>
157
+ <button type="button" data-m="usd">$</button>
158
+ </div>
159
+ <div class="ranges" id="modeToggle">
160
+ <button type="button" data-o="daily" class="active">Daily</button>
161
+ <button type="button" data-o="cumulative">Cumulative</button>
162
+ </div>
163
+ <div class="ranges" id="ranges">
164
+ <button type="button" data-d="7">7D</button>
165
+ <button type="button" data-d="30" class="active">30D</button>
166
+ <button type="button" data-d="90">90D</button>
167
+ <button type="button" data-d="0">All</button>
168
+ </div>
152
169
  </div>
153
170
  </div>
171
+ <div id="chartWrap" class="chartwrap"></div>
172
+ <div id="chartEmpty" class="empty" style="display:none">No compression activity recorded yet. Run <code>enigma compress &lt;file&gt;</code> or enable the compression MCP (<code>enigma config compress on</code>).</div>
154
173
  </div>
155
- <div id="chartWrap" class="chartwrap"></div>
156
- <div id="chartEmpty" class="empty" style="display:none">No compression activity recorded yet. Run <code>enigma compress &lt;file&gt;</code> or enable the compression MCP (<code>enigma config compress on</code>).</div>
157
- </div>
158
-
159
- <div class="panel" id="usagePanel" style="display:none">
160
- <h2 style="margin-bottom:4px">Real Tool Usage <small id="usageSub">Claude Code transcripts</small></h2>
161
- <div class="sub" style="margin-bottom:14px">Measured token consumption and real prompt-cache savings, read-only from your own session logs. Deliberately not attributed to skills or output-style: a transcript has no counterfactual baseline, so any such number would be invented.</div>
162
- <div class="grid" style="margin-bottom:16px">
163
- <div class="card"><div class="label">Cache Saved</div><div id="uCacheMoney" class="value good">-</div><div class="foot">Est. from prompt caching</div></div>
164
- <div class="card"><div class="label">Cache Reads</div><div id="uCacheRead" class="value accent">-</div><div class="foot">Tokens served from cache</div></div>
165
- <div class="card"><div class="label">Input Tokens</div><div id="uInput" class="value">-</div><div class="foot">Consumed across sessions</div></div>
166
- <div class="card"><div class="label">Output Tokens</div><div id="uOutput" class="value">-</div><div class="foot">Generated by the agent</div></div>
167
- <div class="card"><div class="label">Sessions</div><div id="uSessions" class="value">-</div><div id="uSessionsFoot" class="foot">Transcripts scanned</div></div>
174
+
175
+ <div class="panel" id="usagePanel" style="display:none">
176
+ <h2 style="margin-bottom:4px">Real Tool Usage <small id="usageSub">Claude Code transcripts</small></h2>
177
+ <div class="sub" style="margin-bottom:14px">Measured token consumption and real prompt-cache savings, read-only from your own session logs. Deliberately not attributed to skills or output-style: a transcript has no counterfactual baseline, so any such number would be invented.</div>
178
+ <div class="grid" style="margin-bottom:16px">
179
+ <div class="card"><div class="label">Cache Saved</div><div id="uCacheMoney" class="value good">-</div><div class="foot">Est. from prompt caching</div></div>
180
+ <div class="card"><div class="label">Cache Reads</div><div id="uCacheRead" class="value accent">-</div><div class="foot">Tokens served from cache</div></div>
181
+ <div class="card"><div class="label">Input Tokens</div><div id="uInput" class="value">-</div><div class="foot">Consumed across sessions</div></div>
182
+ <div class="card"><div class="label">Output Tokens</div><div id="uOutput" class="value">-</div><div class="foot">Generated by the agent</div></div>
183
+ <div class="card"><div class="label">Sessions</div><div id="uSessions" class="value">-</div><div id="uSessionsFoot" class="foot">Transcripts scanned</div></div>
184
+ </div>
185
+ <h2 style="margin:0 0 10px">By Model</h2>
186
+ <div id="uByModel"></div>
187
+ </div>
188
+
189
+ <div class="panel" id="usageHint" style="display:none">
190
+ <h2 style="margin-bottom:8px">Real Tool Usage <small>opt-in</small></h2>
191
+ <div class="sub">Show real token consumption and prompt-cache savings from your Claude Code sessions in <a href="#/settings">Settings</a> (Real tool-usage stats). enigma reads only your local session transcripts; nothing is sent anywhere.</div>
192
+ </div>
193
+
194
+ <div class="panel">
195
+ <h2 style="margin-bottom:14px">Savings By Source <small>which app/CLI compressed</small></h2>
196
+ <div id="sources"></div>
197
+ </div>
198
+
199
+ <div class="panel">
200
+ <h2 style="margin-bottom:14px">Savings By Content Type <small>what enigma compressed</small></h2>
201
+ <div id="types"></div>
168
202
  </div>
169
- <h2 style="margin:0 0 10px">By Model</h2>
170
- <div id="uByModel"></div>
171
- </div>
172
-
173
- <div class="panel" id="usageHint" style="display:none">
174
- <h2 style="margin-bottom:8px">Real Tool Usage <small>opt-in</small></h2>
175
- <div class="sub">Show real token consumption and prompt-cache savings from your Claude Code sessions with <code>enigma config usage-stats on</code>. enigma reads only your local session transcripts; nothing is sent anywhere.</div>
176
- </div>
177
-
178
- <div class="panel">
179
- <h2 style="margin-bottom:14px">Savings By Source <small>which app/CLI compressed</small></h2>
180
- <div id="sources"></div>
181
- </div>
182
-
183
- <div class="panel">
184
- <h2 style="margin-bottom:14px">Savings By Content Type <small>what enigma compressed</small></h2>
185
- <div id="types"></div>
186
- </div>
187
-
188
- <div class="panel">
189
- <h2 style="margin-bottom:14px">Before vs. After <small id="ratioLabel">cumulative tokens</small></h2>
190
- <div class="bar-row"><span class="name">Before</span><div class="bar-track"><div id="barBefore" class="bar-fill" style="background:var(--accent2)"></div></div><span id="numBefore" class="num">-</span></div>
191
- <div class="bar-row"><span class="name">After</span><div class="bar-track"><div id="barAfter" class="bar-fill" style="background:var(--good)"></div></div><span id="numAfter" class="num">-</span></div>
192
- <div class="stat-line" style="margin:14px 0 0"><div>Removed <b id="removed">-</b></div><div>Reduction <b id="reduction">-</b></div><div>Compression ratio <b id="ratio">-</b></div></div>
193
- </div>
194
-
195
- <div class="panel">
196
- <div class="panel-head">
197
- <h2>Savings History <small id="histStats"></small></h2>
198
- <div class="ctrls">
199
- <div class="ranges" id="histToggle">
200
- <button type="button" data-p="day" class="active">Daily</button>
201
- <button type="button" data-p="week">Weekly</button>
202
- <button type="button" data-p="month">Monthly</button>
203
+
204
+ <div class="panel">
205
+ <h2 style="margin-bottom:14px">Before vs. After <small id="ratioLabel">cumulative tokens</small></h2>
206
+ <div class="bar-row"><span class="name">Before</span><div class="bar-track"><div id="barBefore" class="bar-fill" style="background:var(--accent2)"></div></div><span id="numBefore" class="num">-</span></div>
207
+ <div class="bar-row"><span class="name">After</span><div class="bar-track"><div id="barAfter" class="bar-fill" style="background:var(--good)"></div></div><span id="numAfter" class="num">-</span></div>
208
+ <div class="stat-line" style="margin:14px 0 0"><div>Removed <b id="removed">-</b></div><div>Reduction <b id="reduction">-</b></div><div>Compression ratio <b id="ratio">-</b></div></div>
209
+ </div>
210
+
211
+ <div class="panel">
212
+ <div class="panel-head">
213
+ <h2>Savings History <small id="histStats"></small></h2>
214
+ <div class="ctrls">
215
+ <div class="ranges" id="histToggle">
216
+ <button type="button" data-p="day" class="active">Daily</button>
217
+ <button type="button" data-p="week">Weekly</button>
218
+ <button type="button" data-p="month">Monthly</button>
219
+ </div>
203
220
  </div>
204
221
  </div>
222
+ <div class="tbl-wrap"><div id="histBody"></div></div>
223
+ </div>
224
+
225
+ <div class="panel">
226
+ <h2 style="margin-bottom:14px">Recent Compressions <small>newest first</small></h2>
227
+ <div class="tbl-wrap"><div id="recent"></div></div>
228
+ </div>
229
+
230
+ <div class="panel">
231
+ <h2 style="margin-bottom:14px">Reversible Cache (CCR) <small>recoverable originals on disk</small></h2>
232
+ <div id="cache"></div>
233
+ </div>
234
+ </main>
235
+
236
+ <section id="view-settings" style="display:none">
237
+ <div class="page-head">
238
+ <h1>Settings</h1>
239
+ <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>
205
240
  </div>
206
- <div class="tbl-wrap"><div id="histBody"></div></div>
207
- </div>
208
-
209
- <div class="panel">
210
- <h2 style="margin-bottom:14px">Recent Compressions <small>newest first</small></h2>
211
- <div class="tbl-wrap"><div id="recent"></div></div>
212
- </div>
213
-
214
- <div class="panel">
215
- <h2 style="margin-bottom:14px">Reversible Cache (CCR) <small>recoverable originals on disk</small></h2>
216
- <div id="cache"></div>
217
- </div>
218
-
219
- <div class="panel">
220
- <h2 style="margin-bottom:4px">Enigma Settings <small>same options as the terminal UI</small></h2>
221
- <div class="sub" style="margin-bottom:14px">Everything configurable from <code>enigma config</code> / the TUI, editable here. Changes apply at global scope and take effect immediately; memory toggles need an agent restart. Numeric estimates use <code>enigma config token-price &lt;usd&gt;</code> and <code>enigma config token-speed &lt;tok/s&gt;</code>.</div>
222
- <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
223
- <div id="settingsNote" class="set-note"></div>
224
- </div>
241
+ <div class="panel">
242
+ <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
243
+ <div id="settingsNote" class="set-note"></div>
244
+ </div>
245
+ </section>
225
246
 
226
247
  <footer>
227
248
  Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
@@ -579,7 +600,7 @@
579
600
  }
580
601
  }
581
602
 
582
- // --- settings panel (mirrors the TUI registry over /api/settings) ---
603
+ // --- settings subpage (mirrors the TUI registry over /api/settings) ---
583
604
  function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c])); }
584
605
 
585
606
  function settingRow(s) {
@@ -627,6 +648,7 @@
627
648
  });
628
649
  }
629
650
 
651
+ let settingsLoaded = false;
630
652
  async function loadSettings() {
631
653
  try {
632
654
  const res = await fetch("/api/settings", { cache: "no-store" });
@@ -634,12 +656,24 @@
634
656
  } catch { $("settingsBody").innerHTML = '<div class="empty" style="padding:24px 0">Settings unavailable.</div>'; }
635
657
  }
636
658
 
659
+ // --- hash routing between the Savings and Settings subpages ---
660
+ function route() {
661
+ const v = (location.hash || "").replace(/^#\/?/, "") === "settings" ? "settings" : "savings";
662
+ $("view-savings").style.display = v === "savings" ? "" : "none";
663
+ $("view-settings").style.display = v === "settings" ? "" : "none";
664
+ document.querySelectorAll(".tab").forEach((t) => t.classList.toggle("active", t.dataset.view === v));
665
+ if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
666
+ // The chart was sized while its view may have been hidden; nudge it on return.
667
+ if (v === "savings" && chart) { try { applyRange(); } catch { /* not ready */ } }
668
+ }
669
+
637
670
  initChart();
638
671
  poll();
639
672
  setInterval(poll, POLL_MS);
640
673
  document.addEventListener("visibilitychange", () => { if (!document.hidden) poll(); });
641
674
  wireSettings();
642
- loadSettings();
675
+ window.addEventListener("hashchange", route);
676
+ route();
643
677
  </script>
644
678
  </body>
645
679
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": [