Skip to content

Commit 8f1a701

Browse files
Nigel TatschnerNigel Tatschner
authored andcommitted
release: v0.0.3-beta — tray UI metrics redesign
Carries the metrics-display redesign across to the Tauri desktop window. v0.0.2-beta shipped the new charts to the web app only; the tray was deferred. This closes that gap. * Tray: new EventSparkline component — 48h rolling sparkline of events/hour, inline SVG against --accent (no chart library, no Tauri bundle bloat). Lives above the existing Top event types card in StatusPane.tsx under heading 'Recent activity · 48h'. Consumes the timeline the tray already fetches; no new IPC. Bumps: * workspace 0.0.2-beta -> 0.0.3-beta * tauri.conf.json 0.0.2 -> 0.0.3 The auto-updater will surface this to existing v0.0.2-beta installs once Release tray ships beta.json (the workflow fixes from this release cycle also land here).
1 parent e0588ff commit 8f1a701

6 files changed

Lines changed: 177 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,39 @@ Tag-suffix → release-channel mapping (see `release-manifests/`):
3232

3333
- (nothing yet)
3434

35+
## [0.0.3-beta] — 2026-05-12
36+
37+
Tray-UI half of the metrics-display redesign. v0.0.2-beta shipped the
38+
new charts on the web app only; this release brings a tray-native
39+
equivalent so the desktop window also benefits.
40+
41+
### Added
42+
43+
- **Tray:** `EventSparkline` component — 48-hour rolling sparkline
44+
of events/hour, rendered inline-SVG against the `--accent` token
45+
(no chart library — keeps the Tauri bundle slim). Lands in
46+
`StatusPane.tsx` above the existing "Top event types" card under
47+
the heading "Recent activity · 48h". Consumes the timeline the
48+
tray already fetches; no new IPC.
49+
50+
### CI
51+
52+
- Workflow split: container/config images now live in a sibling
53+
`release-images.yml` workflow so a registry-side outage no longer
54+
marks the tray release as failed. Both workflows trigger on the
55+
same `v*` tag and can be re-run independently via
56+
`workflow_dispatch`.
57+
- `Release tray` now detects already-published GitHub Releases and
58+
skips the asset-upload + draft-promotion steps, so re-runs on
59+
already-shipped tags no longer fail at "Cannot delete asset from
60+
an immutable release". The channel-manifest commit step stays
61+
unguarded so it can still recover a missing manifest.
62+
- Channel-manifest commit step now uses `git add` + `git diff
63+
--cached --quiet` instead of `git diff --quiet` against an
64+
untracked path — fixes the bug that silently skipped the
65+
first-ever `release-manifests/beta.json` publish in v0.0.1-beta
66+
and v0.0.2-beta.
67+
3568
## [0.0.2-beta] — 2026-05-12
3669

3770
Metrics-display redesign, first wave. Replaces the hand-rolled 30-day

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ members = [
77
]
88

99
[workspace.package]
10-
version = "0.0.2-beta"
10+
version = "0.0.3-beta"
1111
edition = "2021"
1212
license = "MPL-2.0"
1313
authors = ["StarStats contributors"]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* EventSparkline — inline-SVG sparkline of events/hour for the tray.
3+
*
4+
* Takes a `TimelineEntry[]` (newest-first, from `getSessionTimeline()`),
5+
* buckets into hourly counts over the last 48 hours, and draws a
6+
* 220×36 SVG line against the `--accent` token so theme swaps repaint
7+
* automatically. No chart library — the tray binary already carries
8+
* enough weight.
9+
*/
10+
11+
import type { TimelineEntry } from '../api';
12+
13+
const W = 220;
14+
const H = 36;
15+
const BUCKETS = 48; // hours
16+
17+
function bucketize(entries: TimelineEntry[]): number[] {
18+
const now = Date.now();
19+
const hourMs = 60 * 60 * 1000;
20+
const buckets = new Array<number>(BUCKETS).fill(0);
21+
for (const e of entries) {
22+
const ts = Date.parse(e.timestamp);
23+
if (!Number.isFinite(ts)) continue;
24+
const age = now - ts;
25+
if (age < 0 || age >= BUCKETS * hourMs) continue;
26+
const idx = BUCKETS - 1 - Math.floor(age / hourMs);
27+
if (idx >= 0 && idx < BUCKETS) buckets[idx] += 1;
28+
}
29+
return buckets;
30+
}
31+
32+
function buildPath(series: number[]): string {
33+
if (series.length === 0) return '';
34+
const max = Math.max(...series, 1);
35+
const stepX = W / Math.max(series.length - 1, 1);
36+
return series
37+
.map((v, i) => {
38+
const x = i * stepX;
39+
const y = H - (v / max) * (H - 2) - 1;
40+
return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`;
41+
})
42+
.join(' ');
43+
}
44+
45+
function buildArea(series: number[]): string {
46+
const line = buildPath(series);
47+
if (line.length === 0) return '';
48+
return `${line} L${W.toFixed(1)},${H.toFixed(1)} L0,${H.toFixed(1)} Z`;
49+
}
50+
51+
export interface EventSparklineProps {
52+
entries: TimelineEntry[];
53+
}
54+
55+
export function EventSparkline({ entries }: EventSparklineProps) {
56+
const series = bucketize(entries);
57+
const total = series.reduce((s, v) => s + v, 0);
58+
const hasData = total > 0;
59+
const peak = Math.max(...series);
60+
61+
return (
62+
<div
63+
style={{ display: 'flex', flexDirection: 'column', gap: 4 }}
64+
role="group"
65+
aria-label={`${total} events in the last 48 hours`}
66+
>
67+
<div
68+
style={{
69+
display: 'flex',
70+
alignItems: 'baseline',
71+
gap: 8,
72+
fontSize: 12,
73+
}}
74+
>
75+
<span
76+
style={{
77+
fontFamily: 'var(--font-mono)',
78+
fontSize: 16,
79+
fontWeight: 600,
80+
color: 'var(--fg)',
81+
}}
82+
>
83+
{total.toLocaleString()}
84+
</span>
85+
<span
86+
style={{
87+
color: 'var(--fg-muted)',
88+
textTransform: 'uppercase',
89+
letterSpacing: '0.06em',
90+
}}
91+
>
92+
events · 48h
93+
</span>
94+
{hasData ? (
95+
<span
96+
style={{ marginLeft: 'auto', color: 'var(--fg-dim)', fontSize: 11 }}
97+
>
98+
peak {peak}/h
99+
</span>
100+
) : null}
101+
</div>
102+
{hasData ? (
103+
<svg
104+
width={W}
105+
height={H}
106+
viewBox={`0 0 ${W} ${H}`}
107+
style={{ display: 'block', width: '100%', maxWidth: W }}
108+
role="img"
109+
aria-label={`Hourly event count over the last ${BUCKETS} hours`}
110+
>
111+
<path d={buildArea(series)} fill="var(--accent-soft)" stroke="none" />
112+
<path
113+
d={buildPath(series)}
114+
fill="none"
115+
stroke="var(--accent)"
116+
strokeWidth={1.5}
117+
strokeLinecap="round"
118+
strokeLinejoin="round"
119+
/>
120+
</svg>
121+
) : (
122+
<div
123+
style={{ height: H, background: 'var(--surface-2)', borderRadius: 4 }}
124+
aria-hidden="true"
125+
/>
126+
)}
127+
</div>
128+
);
129+
}

apps/tray-ui/src/components/StatusPane.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
TONE_VAR,
2525
} from './tray/format';
2626
import type { HangarStats } from '../api';
27+
import { EventSparkline } from './EventSparkline';
2728

2829
/// Only http(s) origins get rendered as a clickable link in the
2930
/// email-verification banner. Defends against a hostile local config
@@ -356,6 +357,15 @@ export function StatusPane({ status, webOrigin, onGoToSettings }: Props) {
356357
</TrayCard>
357358
</div>
358359

360+
{/* 48-hour activity sparkline — buckets the in-memory timeline
361+
into hourly counts. Same data as Top types below, just shaped
362+
for "when did stuff happen" instead of "what kinds happened". */}
363+
{timeline && timeline.length > 0 ? (
364+
<TrayCard title="Recent activity" kicker="48h">
365+
<EventSparkline entries={timeline} />
366+
</TrayCard>
367+
) : null}
368+
359369
{/* TOP TYPES */}
360370
{event_counts.length === 0 ? (
361371
<TrayCard title="Top event types">

crates/starstats-client/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "StarStats",
4-
"version": "0.0.2",
4+
"version": "0.0.3",
55
"identifier": "app.starstats.tray",
66
"build": {
77
"beforeDevCommand": "pnpm --filter tray-ui dev",

0 commit comments

Comments
 (0)