mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
feat(header): rings for CPU and MEM in the Compact style (#426)
Compact now draws every reading the same way, ring then label then value: CPU and MEM get an accent ring (red past 80%, like their value) in place of the sparklines, the plan windows keep their green, yellow or red rings, the dot separators go, and both pills share one label and one value style. The sparkline markup, history and CSS are removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -335,7 +335,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
|||||||
|
|
||||||
**Tab layouts** (`tabArrangement`, per-device, default `state`; Discussion #426): `state` (option C) splits the list into needs you / waiting / working / idle (idle is `quiet`: its heading stays as the row's anchor but draws no label; `tabStateOrder: 'urgent-last'` flips the order, needs you at the bottom): rows in the desktop header strip with left-aligned labels in a measured column (the brand sits over the strip's corner, so the rows after the first start under "Codeman"), sections in the flat rail and the sidebar, inline dividers on the tablet strip, group order with no headings on phones. `case` (option A) wraps each case's tabs in one `.tab-cluster` box and hides the `-<case>` of a generated name (`.tab-name-case`). `ledger` (option B) is a CSS-only grid on the desktop header strip. `classic` is the old strip. Pure cores: `CodemanTabTriage` and `CodemanTabClusters` (constants.js). ⚠️ `state` is the flex `order` property plus `aria-hidden` heading/break elements reconciled in place by `_syncTabTriageChrome()` after BOTH render paths, never a DOM reorder (a state change is an incremental pass). ⚠️ `case` boxes ARE a DOM grouping, so the incremental path rebuilds whenever `_tabClusterLayout().key` changes; membership follows `_mobileOverviewCaseFor()` like the home screens. ⚠️ Drag only reorders within a group or box (`_isTabDropAcrossGroups()`). ⚠️ Named groups in the vertical rail win over both groupings. ⚠️ `classic` must leave no trace (no headings, no inline order, no boxes, no name split), and the ledger must not change the markup. Tests: `test/tab-triage.test.ts`, `test/tab-clusters.test.ts`. → [architecture-invariants#tab-layouts-by-state-by-case-ledger](docs/architecture-invariants.md#tab-layouts-by-state-by-case-ledger)
|
**Tab layouts** (`tabArrangement`, per-device, default `state`; Discussion #426): `state` (option C) splits the list into needs you / waiting / working / idle (idle is `quiet`: its heading stays as the row's anchor but draws no label; `tabStateOrder: 'urgent-last'` flips the order, needs you at the bottom): rows in the desktop header strip with left-aligned labels in a measured column (the brand sits over the strip's corner, so the rows after the first start under "Codeman"), sections in the flat rail and the sidebar, inline dividers on the tablet strip, group order with no headings on phones. `case` (option A) wraps each case's tabs in one `.tab-cluster` box and hides the `-<case>` of a generated name (`.tab-name-case`). `ledger` (option B) is a CSS-only grid on the desktop header strip. `classic` is the old strip. Pure cores: `CodemanTabTriage` and `CodemanTabClusters` (constants.js). ⚠️ `state` is the flex `order` property plus `aria-hidden` heading/break elements reconciled in place by `_syncTabTriageChrome()` after BOTH render paths, never a DOM reorder (a state change is an incremental pass). ⚠️ `case` boxes ARE a DOM grouping, so the incremental path rebuilds whenever `_tabClusterLayout().key` changes; membership follows `_mobileOverviewCaseFor()` like the home screens. ⚠️ Drag only reorders within a group or box (`_isTabDropAcrossGroups()`). ⚠️ Named groups in the vertical rail win over both groupings. ⚠️ `classic` must leave no trace (no headings, no inline order, no boxes, no name split), and the ledger must not change the markup. Tests: `test/tab-triage.test.ts`, `test/tab-clusters.test.ts`. → [architecture-invariants#tab-layouts-by-state-by-case-ledger](docs/architecture-invariants.md#tab-layouts-by-state-by-case-ledger)
|
||||||
|
|
||||||
**Header stats styles** (`headerStatsStyle`, per-device, desktop only, default `tiles`; Discussion #426 option G): `tiles` / `compact` (pill + sparklines + plan rings) / `classic` (as before), keyed on `data-header-stats` (pre-paint + `applyHeaderStatsStyle()`), forced to `classic` below 768px and in a solo window. ⚠️ The clustered styles MOVE `#connectionIndicator` into `#headerSystemStats` and `#planUsageChip` right after it; `classic` moves them back to comment anchors. ⚠️ WS joins the pill only while System Stats is shown. ⚠️ The extra parts (sparklines, tile words, rings, meters) are always rendered and hidden by default in CSS, which is what keeps `classic` unchanged; tile words are derived beside the connection descriptor, never added to it (`test/connection-indicator.test.ts` pins its shape). ⚠️ A tile is a three-row grid (label, value, bar) with pixel line-heights and the bundled JetBrains Mono; never lay the bar over a fixed-height tile (a taller system mono put the value on top of it). The WS tile's grid lives on the inner `.connection-tile`, because JS writes the indicator's `display` inline. Tests: `test/header-stats-style.test.ts`. → [architecture-invariants#header-stats-styles](docs/architecture-invariants.md#header-stats-styles)
|
**Header stats styles** (`headerStatsStyle`, per-device, desktop only, default `tiles`; Discussion #426 option G): `tiles` / `compact` (two pills, a ring beside every value) / `classic` (as before), keyed on `data-header-stats` (pre-paint + `applyHeaderStatsStyle()`), forced to `classic` below 768px and in a solo window. ⚠️ The clustered styles MOVE `#connectionIndicator` into `#headerSystemStats` and `#planUsageChip` right after it; `classic` moves them back to comment anchors. ⚠️ WS joins the pill only while System Stats is shown. ⚠️ The extra parts (stat rings, tile words, plan rings, meters) are always rendered and hidden by default in CSS, which is what keeps `classic` unchanged; tile words are derived beside the connection descriptor, never added to it (`test/connection-indicator.test.ts` pins its shape). ⚠️ A tile is a three-row grid (label, value, bar) with pixel line-heights and the bundled JetBrains Mono; never lay the bar over a fixed-height tile (a taller system mono put the value on top of it). The WS tile's grid lives on the inner `.connection-tile`, because JS writes the indicator's `display` inline. Tests: `test/header-stats-style.test.ts`. → [architecture-invariants#header-stats-styles](docs/architecture-invariants.md#header-stats-styles)
|
||||||
|
|
||||||
**Phone overview home screen** (`mobile-overview.js`, per-device `mobileOverviewEnabled`, default ON): under 600px the "C" logo shows NEEDS YOU / CURRENT / PAST SESSIONS instead of the welcome overlay, branched in `showWelcome()`/`hideWelcome()` via width-driven `shouldUseMobileOverview()`. ⚠️ The container ships `hidden` and only this module removes it: never give `.mobile-overview` a bare `display` rule (desktop does not load `mobile.css`). ⚠️ The split Run button must carry the toolbar's own classes (`btn-toolbar btn-run mode-<backend>` / `btn-run-gear`) and mobile.css must set no `background`/`color` on it; row status must mirror the session-tab alert language. PAST rows resume through the shared `resumeHistorySession()`. Status pills carry `data-i18n-skip`. → [architecture-invariants#phone-overview-home-screen](docs/architecture-invariants.md#phone-overview-home-screen)
|
**Phone overview home screen** (`mobile-overview.js`, per-device `mobileOverviewEnabled`, default ON): under 600px the "C" logo shows NEEDS YOU / CURRENT / PAST SESSIONS instead of the welcome overlay, branched in `showWelcome()`/`hideWelcome()` via width-driven `shouldUseMobileOverview()`. ⚠️ The container ships `hidden` and only this module removes it: never give `.mobile-overview` a bare `display` rule (desktop does not load `mobile.css`). ⚠️ The split Run button must carry the toolbar's own classes (`btn-toolbar btn-run mode-<backend>` / `btn-run-gear`) and mobile.css must set no `background`/`color` on it; row status must mirror the session-tab alert language. PAST rows resume through the shared `resumeHistorySession()`. Status pills carry `data-i18n-skip`. → [architecture-invariants#phone-overview-home-screen](docs/architecture-invariants.md#phone-overview-home-screen)
|
||||||
|
|
||||||
|
|||||||
@@ -885,11 +885,11 @@ With `classic` nothing is left behind: no headings, no breaks, no inline order,
|
|||||||
|
|
||||||
### Header stats styles
|
### Header stats styles
|
||||||
|
|
||||||
**Header stats styles** (`headerStatsStyle`, per-device, desktop only, default `tiles`; Discussion #426 option G): how the WS readout, CPU, MEM and the plan-usage windows are drawn. `tiles` gives each its own label-over-value tile with a bar along the bottom edge; `compact` is one `WS · CPU · MEM` pill with a 4-sample sparkline per stat plus a pill with a ring per plan window; `classic` is the bars and `5H · 7D` chip as before. `data-header-stats` on `<html>` (pre-paint, then `applyHeaderStatsStyle()` from `applyHeaderVisibilitySettings()`) drives every rule; narrower than 768px and in a solo window it resolves to `classic`, because the stats are hidden there anyway and the moves below would otherwise reach the phone header.
|
**Header stats styles** (`headerStatsStyle`, per-device, desktop only, default `tiles`; Discussion #426 option G): how the WS readout, CPU, MEM and the plan-usage windows are drawn. `tiles` gives each its own label-over-value tile with a bar along the bottom edge; `compact` is two pills, WS / CPU / MEM and the plan windows, with a ring beside every value (stat rings accent, red past 80%; plan rings green, yellow or red); `classic` is the bars and `5H · 7D` chip as before. `data-header-stats` on `<html>` (pre-paint, then `applyHeaderStatsStyle()` from `applyHeaderVisibilitySettings()`) drives every rule; narrower than 768px and in a solo window it resolves to `classic`, because the stats are hidden there anyway and the moves below would otherwise reach the phone header.
|
||||||
|
|
||||||
⚠️ The clustered styles need the three elements contiguous, and the template keeps the classic order, so `applyHeaderStatsStyle()` MOVES `#connectionIndicator` into `#headerSystemStats` (first child) and `#planUsageChip` right after it, and `classic` moves them back to comment anchors it left at the template positions. Ids are unchanged, so every writer still finds them. ⚠️ The indicator joins the pill only while System Stats is shown: the pill is hidden with `display: none`, and the WS readout must not disappear with it.
|
⚠️ The clustered styles need the three elements contiguous, and the template keeps the classic order, so `applyHeaderStatsStyle()` MOVES `#connectionIndicator` into `#headerSystemStats` (first child) and `#planUsageChip` right after it, and `classic` moves them back to comment anchors it left at the template positions. Ids are unchanged, so every writer still finds them. ⚠️ The indicator joins the pill only while System Stats is shown: the pill is hidden with `display: none`, and the WS readout must not disappear with it.
|
||||||
|
|
||||||
⚠️ The extra parts (`.stat-spark`, `.connection-tile`, `.pu-ring`, `.pu-meter`) are always rendered and hidden by default in styles.css, which is what keeps `classic` looking exactly as before. ⚠️ A tile is a three-row grid (9px label, 14px value, 2px bar) with pixel line-heights, 36px tall like the header, in the bundled JetBrains Mono. The first version laid the bar over the bottom of a fixed 28px tile, and a taller system mono (SF Mono) pushed the value into it; the bar is now a real row, so the height comes from the layout, not the font. The WS tile keeps its grid on the inner `.connection-tile` span, because `_updateConnectionIndicator()` writes the indicator's own `display` inline. The tile words come from `_connectionTileWords()`, DERIVED from the connection descriptor rather than added to it, because `test/connection-indicator.test.ts` pins the descriptor's exact shape. Plan rings and meters clamp their fill to 0-100 while the label keeps the real number. Tests: `test/header-stats-style.test.ts`.
|
⚠️ The extra parts (`.stat-ring`, `.connection-tile`, `.pu-ring`, `.pu-meter`) are always rendered and hidden by default in styles.css, which is what keeps `classic` looking exactly as before. ⚠️ A tile is a three-row grid (9px label, 14px value, 2px bar) with pixel line-heights, 36px tall like the header, in the bundled JetBrains Mono. The first version laid the bar over the bottom of a fixed 28px tile, and a taller system mono (SF Mono) pushed the value into it; the bar is now a real row, so the height comes from the layout, not the font. The WS tile keeps its grid on the inner `.connection-tile` span, because `_updateConnectionIndicator()` writes the indicator's own `display` inline. The tile words come from `_connectionTileWords()`, DERIVED from the connection descriptor rather than added to it, because `test/connection-indicator.test.ts` pins the descriptor's exact shape. Plan rings and meters clamp their fill to 0-100 while the label keeps the real number. Tests: `test/header-stats-style.test.ts`.
|
||||||
|
|
||||||
### Phone overview home screen
|
### Phone overview home screen
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ window; off lists every file by its full path.
|
|||||||
|
|
||||||
Most default to off. The stock desktop header is system stats, File Viewer, and the gear.
|
Most default to off. The stock desktop header is system stats, File Viewer, and the gear.
|
||||||
**Header Stats Style** picks how the system stats and plan usage are drawn: *Tiles*
|
**Header Stats Style** picks how the system stats and plan usage are drawn: *Tiles*
|
||||||
(default; label over value with a bar underneath), *Compact* (one pill with sparklines plus
|
(default; label over value with a bar underneath), *Compact* (two pills with a ring beside every value) or *As before* (the bars and the `5H · 7D` chip). Desktop only, per device.
|
||||||
plan-usage rings) or *As before* (the bars and the `5H · 7D` chip). Desktop only, per device.
|
|
||||||
New header controls never appear on phones. Split is desktop-only regardless of this
|
New header controls never appear on phones. Split is desktop-only regardless of this
|
||||||
setting — the button and the feature both stay off below a ~1180px viewport, where two
|
setting — the button and the feature both stay off below a ~1180px viewport, where two
|
||||||
resizable panes plus their divider have nowhere to go.
|
resizable panes plus their divider have nowhere to go.
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ The connection readout, CPU, MEM and the plan usage windows can be drawn three w
|
|||||||
| Style | Look |
|
| Style | Look |
|
||||||
| -------------- | -------------------------------------------------------------------------------------- |
|
| -------------- | -------------------------------------------------------------------------------------- |
|
||||||
| **Tiles** | The default. One small tile each (`WS live`, `CPU 22%`, `MEM 14.4G`, `5H 28%`, `7D 35%`): label over value, a thin bar underneath, no icons. |
|
| **Tiles** | The default. One small tile each (`WS live`, `CPU 22%`, `MEM 14.4G`, `5H 28%`, `7D 35%`): label over value, a thin bar underneath, no icons. |
|
||||||
| **Compact** | One pill with `WS · CPU · MEM` and a tiny sparkline of the last few samples, then a pill with a ring per plan window. Hands the tabs back the most room. |
|
| **Compact** | Two slim pills, `WS · CPU · MEM` and the plan windows, with a small ring beside every value. Hands the tabs back the most room. |
|
||||||
| **As before** | The bars and the `5H · 7D` chip, exactly as they were. |
|
| **As before** | The bars and the `5H · 7D` chip, exactly as they were. |
|
||||||
|
|
||||||
Hiding System Stats or Plan Usage still hides them in every style.
|
Hiding System Stats or Plan Usage still hides them in every style.
|
||||||
|
|||||||
@@ -163,19 +163,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
|
<div class="header-system-stats" id="headerSystemStats" title="System resource usage">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
|
<span class="stat-ring" id="statCpuRing" aria-hidden="true"></span>
|
||||||
<span class="stat-label">CPU</span>
|
<span class="stat-label">CPU</span>
|
||||||
<div class="stat-bar">
|
<div class="stat-bar">
|
||||||
<div class="stat-bar-fill stat-bar-cpu" id="statCpuBar"></div>
|
<div class="stat-bar-fill stat-bar-cpu" id="statCpuBar"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="stat-spark" id="statCpuSpark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
|
|
||||||
<span class="stat-value" id="statCpu">--%</span>
|
<span class="stat-value" id="statCpu">--%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
|
<span class="stat-ring" id="statMemRing" aria-hidden="true"></span>
|
||||||
<span class="stat-label">MEM</span>
|
<span class="stat-label">MEM</span>
|
||||||
<div class="stat-bar">
|
<div class="stat-bar">
|
||||||
<div class="stat-bar-fill stat-bar-mem" id="statMemBar"></div>
|
<div class="stat-bar-fill stat-bar-mem" id="statMemBar"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="stat-spark" id="statMemSpark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
|
|
||||||
<span class="stat-value" id="statMem">--</span>
|
<span class="stat-value" id="statMem">--</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1921,10 +1921,10 @@
|
|||||||
<label class="set-chip" data-preview="header" data-preview-order="13" data-preview-text="42%"><input type="checkbox" id="appSettingsShowPlanUsageLimits"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5"/></svg><span>Plan Usage</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="13" data-preview-text="42%"><input type="checkbox" id="appSettingsShowPlanUsageLimits"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5"/></svg><span>Plan Usage</span></label>
|
||||||
<label class="set-chip" data-preview="header" data-preview-order="14"><input type="checkbox" id="appSettingsShowLifecycleLog"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg><span>Lifecycle Log</span></label>
|
<label class="set-chip" data-preview="header" data-preview-order="14"><input type="checkbox" id="appSettingsShowLifecycleLog"><svg class="set-chip-ico" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg><span>Lifecycle Log</span></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="set-row has-field" data-search="header stats style system cpu mem ws plan usage tiles compact rings sparkline">
|
<div class="set-row has-field" data-search="header stats style system cpu mem ws plan usage tiles compact rings">
|
||||||
<div class="set-row-text">
|
<div class="set-row-text">
|
||||||
<span class="set-row-label">Header Stats Style <span class="set-tag">desktop</span></span>
|
<span class="set-row-label">Header Stats Style <span class="set-tag">desktop</span></span>
|
||||||
<span class="set-row-desc">How WS, CPU, MEM and the plan-usage windows are drawn. Compact is one system pill with sparklines plus usage rings; Tiles put each label over its value with a bar underneath.</span>
|
<span class="set-row-desc">How WS, CPU, MEM and the plan-usage windows are drawn. Compact puts a ring beside each value in two pills; Tiles put each label over its value with a bar underneath.</span>
|
||||||
</div>
|
</div>
|
||||||
<select id="appSettingsHeaderStatsStyle" class="set-select">
|
<select id="appSettingsHeaderStatsStyle" class="set-select">
|
||||||
<option value="classic">As before (bars)</option>
|
<option value="classic">As before (bars)</option>
|
||||||
|
|||||||
+13
-20
@@ -6028,10 +6028,10 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sparklines for the Compact header style. Always recorded (four numbers a
|
// Rings for the Compact header style, kept current in every style (two
|
||||||
// poll), so switching styles shows history at once instead of empty bars.
|
// style writes a poll) so switching styles never shows an empty ring.
|
||||||
this._pushStatSpark('cpu', stats.cpu);
|
this._setStatRing('statCpuRing', stats.cpu);
|
||||||
this._pushStatSpark('mem', stats.memory?.percent);
|
this._setStatRing('statMemRing', stats.memory?.percent);
|
||||||
|
|
||||||
if (memEl && memBar) {
|
if (memEl && memBar) {
|
||||||
const memGB = (stats.memory.usedMB / 1024).toFixed(1);
|
const memGB = (stats.memory.usedMB / 1024).toFixed(1);
|
||||||
@@ -6051,24 +6051,17 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record one percentage sample and redraw that stat's sparkline: one bar per
|
* Fill one stat ring (Compact header style) to `percent`, clamped to 0-100,
|
||||||
* sample, newest on the right, as many samples as the template has bars.
|
* and flag it `high` above 80%, the same threshold at which the value next
|
||||||
* Bars stay 10% tall at minimum so an idle machine still draws a baseline.
|
* to it turns red.
|
||||||
*/
|
*/
|
||||||
_pushStatSpark(key, percent) {
|
_setStatRing(id, percent) {
|
||||||
const history = (this._statSparkHistory ||= { cpu: [], mem: [] })[key];
|
const ring = this.$(id);
|
||||||
if (!history) return;
|
if (!ring) return;
|
||||||
const el = this.$(key === 'cpu' ? 'statCpuSpark' : 'statMemSpark');
|
|
||||||
const size = el?.children.length || 4;
|
|
||||||
const value = Number(percent);
|
const value = Number(percent);
|
||||||
history.push(Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0);
|
const fill = Number.isFinite(value) ? Math.round(Math.min(100, Math.max(0, value))) : 0;
|
||||||
while (history.length > size) history.shift();
|
ring.style.setProperty('--pu', String(fill));
|
||||||
if (!el) return;
|
ring.classList.toggle('high', fill > 80);
|
||||||
const offset = size - history.length;
|
|
||||||
for (let i = 0; i < el.children.length; i++) {
|
|
||||||
const sample = history[i - offset];
|
|
||||||
el.children[i].style.height = sample === undefined ? '' : `${Math.max(10, Math.round(sample))}%`;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ─── Clipboard ──────────────────────────────────────────────────────────────
|
// ─── Clipboard ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+53
-58
@@ -20107,27 +20107,37 @@ html[data-session-list='sidebar'][data-sidebar='collapsed'] .session-sidebar .ta
|
|||||||
it, so the cluster is contiguous. */
|
it, so the cluster is contiguous. */
|
||||||
|
|
||||||
/* Parts only the two new styles draw. */
|
/* Parts only the two new styles draw. */
|
||||||
.stat-spark,
|
.stat-ring,
|
||||||
.connection-tile,
|
.connection-tile,
|
||||||
.header-plan-usage .pu-ring,
|
.header-plan-usage .pu-ring,
|
||||||
.header-plan-usage .pu-meter {
|
.header-plan-usage .pu-meter {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Compact: one system pill with sparklines, one plan-ring pill ------- */
|
/* --- Compact: two pills, a ring beside every value ------------------------ */
|
||||||
html[data-header-stats='compact'] .header-system-stats {
|
/* One system pill (WS, CPU, MEM) and one plan pill (5h, 7d). Every reading is
|
||||||
|
the same shape, ring then label then value, so the two pills read as one
|
||||||
|
set; CPU and MEM rings are the accent colour (red past 80%, like their
|
||||||
|
value), the plan rings green, yellow or red by how much is used. */
|
||||||
|
html[data-header-stats='compact'] .header-system-stats,
|
||||||
|
html[data-header-stats='compact'] .header-plan-usage {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0;
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0 10px;
|
min-height: 24px;
|
||||||
|
padding: 0 11px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: var(--control-bg);
|
background: var(--control-bg);
|
||||||
border: 1px solid var(--control-border);
|
border: 1px solid var(--control-border);
|
||||||
|
font-family: 'JetBrains Mono', var(--font-mono, monospace);
|
||||||
font-size: 0.68rem;
|
font-size: 0.68rem;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[data-header-stats='compact'] .header-system-stats {
|
||||||
|
gap: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-system-stats > .connection-indicator {
|
html[data-header-stats='compact'] .header-system-stats > .connection-indicator {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -20138,66 +20148,46 @@ html[data-header-stats='compact'] .header-system-stats > .connection-indicator >
|
|||||||
height: 7px;
|
height: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Same bundled face as Tiles, so the pills read the same on every machine. */
|
html[data-header-stats='compact'] .header-system-stats .stat-item {
|
||||||
html[data-header-stats='compact'] .header-system-stats,
|
gap: 5px;
|
||||||
html[data-header-stats='compact'] .header-plan-usage {
|
|
||||||
font-family: 'JetBrains Mono', var(--font-mono, monospace);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-system-stats > .connection-indicator::after,
|
|
||||||
html[data-header-stats='compact'] .header-system-stats > .stat-item + .stat-item::before {
|
|
||||||
content: '·';
|
|
||||||
margin: 0 8px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-system-stats .stat-label {
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 0.62rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-system-stats .stat-bar {
|
html[data-header-stats='compact'] .header-system-stats .stat-bar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-system-stats .stat-value {
|
/* One label style and one value style across both pills. */
|
||||||
|
html[data-header-stats='compact'] .header-system-stats .stat-label,
|
||||||
|
html[data-header-stats='compact'] .header-plan-usage .pu-label {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-header-stats='compact'] .header-system-stats .stat-value,
|
||||||
|
html[data-header-stats='compact'] .header-plan-usage .pu-val {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-header-stats='compact'] .header-system-stats .stat-value:not(.high) {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .stat-spark {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 1px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-header-stats='compact'] .stat-spark i {
|
|
||||||
width: 3px;
|
|
||||||
height: 10%;
|
|
||||||
border-radius: 1px;
|
|
||||||
background: var(--accent);
|
|
||||||
transition: height 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage {
|
html[data-header-stats='compact'] .header-plan-usage {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
gap: 13px;
|
||||||
gap: 12px;
|
|
||||||
height: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
padding: 0 10px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: var(--control-bg);
|
|
||||||
border-color: var(--control-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-row {
|
html[data-header-stats='compact'] .header-plan-usage .pu-row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-provider {
|
html[data-header-stats='compact'] .header-plan-usage .pu-provider {
|
||||||
@@ -20207,7 +20197,7 @@ html[data-header-stats='compact'] .header-plan-usage .pu-provider {
|
|||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-windows {
|
html[data-header-stats='compact'] .header-plan-usage .pu-windows {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-sep {
|
html[data-header-stats='compact'] .header-plan-usage .pu-sep {
|
||||||
@@ -20216,27 +20206,32 @@ html[data-header-stats='compact'] .header-plan-usage .pu-sep {
|
|||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-win {
|
html[data-header-stats='compact'] .header-plan-usage .pu-win {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-label {
|
/* A ring: a conic fill masked down to a 2px stroke. Its colour is currentColor,
|
||||||
font-weight: 500;
|
so a plan ring takes the pu-green/-yellow/-red class it shares with its
|
||||||
text-transform: none;
|
value, and a stat ring the accent (red when .high). */
|
||||||
color: var(--text-dim);
|
html[data-header-stats='compact'] .stat-ring,
|
||||||
}
|
|
||||||
|
|
||||||
/* A ring per window: a conic fill masked down to a 2px stroke, coloured like
|
|
||||||
the value next to it (the pu-green/-yellow/-red class sits on both). */
|
|
||||||
html[data-header-stats='compact'] .header-plan-usage .pu-ring {
|
html[data-header-stats='compact'] .header-plan-usage .pu-ring {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
flex: none;
|
flex: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: conic-gradient(currentColor calc(var(--pu, 0) * 1%), var(--control-border) 0);
|
background: conic-gradient(currentColor calc(var(--pu, 0) * 1%), var(--control-border-hover, var(--control-border)) 0);
|
||||||
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
|
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
|
||||||
mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
|
mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[data-header-stats='compact'] .stat-ring {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-header-stats='compact'] .stat-ring.high {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Tiles: label over value, a bar underneath, no icons ----------------- */
|
/* --- Tiles: label over value, a bar underneath, no icons ----------------- */
|
||||||
/* Each tile is a three-row grid (label, value, bar) with pixel line-heights,
|
/* Each tile is a three-row grid (label, value, bar) with pixel line-heights,
|
||||||
so its height comes from the layout, not from whichever monospace face the
|
so its height comes from the layout, not from whichever monospace face the
|
||||||
|
|||||||
+1
-1
@@ -1457,7 +1457,7 @@ export const SettingsUpdateSchema = z
|
|||||||
* How the header draws its WS / CPU / MEM / plan-usage cluster. Display key
|
* How the header draws its WS / CPU / MEM / plan-usage cluster. Display key
|
||||||
* (per-device), desktop only (the cluster is hidden below 768px).
|
* (per-device), desktop only (the cluster is hidden below 768px).
|
||||||
* 'classic' = the bars and the 5H · 7D chip, as before
|
* 'classic' = the bars and the 5H · 7D chip, as before
|
||||||
* 'compact' = one system pill with sparklines plus a plan-usage ring pill
|
* 'compact' = two pills (WS/CPU/MEM, the plan windows), a ring beside every value
|
||||||
* 'tiles' = label over value with a bar underneath, no icons. The default.
|
* 'tiles' = label over value with a bar underneath, no icons. The default.
|
||||||
*/
|
*/
|
||||||
headerStatsStyle: z.enum(['classic', 'compact', 'tiles']).optional(),
|
headerStatsStyle: z.enum(['classic', 'compact', 'tiles']).optional(),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview The header-stats styles (`headerStatsStyle`, Discussion #426
|
* @fileoverview The header-stats styles (`headerStatsStyle`, Discussion #426
|
||||||
* option G): 'classic' (as before), 'compact' (one system pill with sparklines
|
* option G): 'classic' (as before), 'compact' (one system pill with rings
|
||||||
* plus a plan-ring pill) and 'tiles' (label over value, bar underneath; the
|
* plus a plan-ring pill) and 'tiles' (label over value, bar underneath; the
|
||||||
* default).
|
* default).
|
||||||
*
|
*
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* from index.html, and 'classic' puts both back exactly where the template
|
* from index.html, and 'classic' puts both back exactly where the template
|
||||||
* had them.
|
* had them.
|
||||||
* - The WS readout never disappears with a hidden System Stats pill.
|
* - The WS readout never disappears with a hidden System Stats pill.
|
||||||
* - The parts only the new styles draw (sparklines, rings, meters, tile words)
|
* - The parts only the new styles draw (stat rings, plan rings, meters, tile words)
|
||||||
* are rendered with sane values and hidden by default in CSS, which is what
|
* are rendered with sane values and hidden by default in CSS, which is what
|
||||||
* keeps 'classic' looking exactly as before.
|
* keeps 'classic' looking exactly as before.
|
||||||
*
|
*
|
||||||
@@ -182,17 +182,20 @@ describe('the parts the new styles draw', () => {
|
|||||||
expect(document.getElementById('connectionTileValue')!.className).toBe('connection-tile-value connected');
|
expect(document.getElementById('connectionTileValue')!.className).toBe('connection-tile-value connected');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('draws the sparkline newest-right, with a 10% floor and only real samples', () => {
|
it('fills the CPU and MEM rings from the stats poll, clamped, red past 80%', () => {
|
||||||
const app = makeApp();
|
const app = makeApp();
|
||||||
const bars = () =>
|
const ring = (id: string) => {
|
||||||
[...document.querySelectorAll<HTMLElement>('#statCpuSpark > i')].map((bar) => bar.style.height);
|
const el = document.getElementById(id)!;
|
||||||
app._pushStatSpark('cpu', 40);
|
return [el.style.getPropertyValue('--pu'), el.classList.contains('high')];
|
||||||
expect(bars()).toEqual(['', '', '', '40%']);
|
};
|
||||||
app._pushStatSpark('cpu', 2);
|
app.updateSystemStatsDisplay({ cpu: 22, memory: { usedMB: 14.4 * 1024, percent: 45.6 } });
|
||||||
app._pushStatSpark('cpu', 250);
|
expect(ring('statCpuRing')).toEqual(['22', false]);
|
||||||
app._pushStatSpark('cpu', 60);
|
expect(ring('statMemRing')).toEqual(['46', false]);
|
||||||
app._pushStatSpark('cpu', 70);
|
app.updateSystemStatsDisplay({ cpu: 140, memory: { usedMB: 30 * 1024, percent: 81 } });
|
||||||
expect(bars()).toEqual(['10%', '100%', '60%', '70%']);
|
expect(ring('statCpuRing')).toEqual(['100', true]);
|
||||||
|
expect(ring('statMemRing')).toEqual(['81', true]);
|
||||||
|
// A ring comes first in its stat, before the label, like the plan rings.
|
||||||
|
expect(document.getElementById('statCpuRing')!.nextElementSibling!.className).toBe('stat-label');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,7 +204,7 @@ describe('header stats wiring (static)', () => {
|
|||||||
|
|
||||||
it('hides the new-style parts by default, so classic looks exactly as before', () => {
|
it('hides the new-style parts by default, so classic looks exactly as before', () => {
|
||||||
expect(css).toMatch(
|
expect(css).toMatch(
|
||||||
/\.stat-spark,\s*\.connection-tile,\s*\.header-plan-usage \.pu-ring,\s*\.header-plan-usage \.pu-meter \{\s*display: none;/
|
/\.stat-ring,\s*\.connection-tile,\s*\.header-plan-usage \.pu-ring,\s*\.header-plan-usage \.pu-meter \{\s*display: none;/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user