feat(desktop): add Show Dashboard tray item and empty-state copy

Linux tray implementations often do not deliver a distinct left-click event
through the AppIndicator path, so the popover could not be opened on GNOME.
Add an explicit Show Dashboard menu item that calls toggle_popover so the
UI is reachable via the tray menu on every platform.

Also render a friendly empty state in the popover when no session data is
found, pointing at the supported source paths instead of showing a bare
$0 hero with an empty activity list.
This commit is contained in:
AgentSeal 2026-04-18 03:35:37 -07:00
parent d8ff4dcca7
commit 1eef50a64d
3 changed files with 53 additions and 16 deletions

View file

@ -64,10 +64,11 @@ pub fn run() {
}
fn build_tray(app: &AppHandle) -> tauri::Result<()> {
let dashboard = MenuItem::with_id(app, "dashboard", "Show Dashboard", true, None::<&str>)?;
let refresh = MenuItem::with_id(app, "refresh", "Refresh", true, None::<&str>)?;
let report = MenuItem::with_id(app, "report", "Open Full Report", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "Quit CodeBurn", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&refresh, &report, &quit])?;
let menu = Menu::with_items(app, &[&dashboard, &refresh, &report, &quit])?;
TrayIconBuilder::with_id("codeburn-tray")
.tooltip("CodeBurn")
@ -75,6 +76,7 @@ fn build_tray(app: &AppHandle) -> tauri::Result<()> {
.show_menu_on_left_click(false)
.on_menu_event(|app, event| match event.id.as_ref() {
"quit" => app.exit(0),
"dashboard" => toggle_popover(app),
"refresh" => {
// Nudge the webview so it re-requests the payload. The front-end listens for
// this event and kicks off a new fetch_payload command.

View file

@ -124,22 +124,40 @@ export function App() {
))}
</nav>
<section className="activity">
<h2 className="section-title">Activity</h2>
{payload.current.topActivities.length === 0 && (
<p className="empty">No activity for this period.</p>
)}
{payload.current.topActivities.map(a => (
<div key={a.name} className="row">
<div className="row-label">{a.name}</div>
<div className="row-cost">{formatCompactCurrency(a.cost, currency)}</div>
<div className="row-turns">{a.turns}</div>
<div className="row-oneshot">
{a.oneShotRate == null ? '—' : `${Math.round(a.oneShotRate * 100)}%`}
{!loading && payload.current.calls === 0 && payload.current.sessions === 0 ? (
<section className="empty-state">
<h2 className="section-title">No session data yet</h2>
<p>
CodeBurn reads local session logs from your AI coding tools. It looks like
none of the supported tools have written any sessions on this machine yet.
</p>
<p>Supported sources:</p>
<ul>
<li><code>~/.claude/projects/</code> (Claude Code)</li>
<li><code>~/.codex/sessions/</code> (Codex CLI)</li>
<li>Cursor IDE local database</li>
<li>GitHub Copilot session events</li>
</ul>
<p>Run one of those tools for a session, then hit Refresh.</p>
</section>
) : (
<section className="activity">
<h2 className="section-title">Activity</h2>
{payload.current.topActivities.length === 0 && (
<p className="empty">No activity for this period.</p>
)}
{payload.current.topActivities.map(a => (
<div key={a.name} className="row">
<div className="row-label">{a.name}</div>
<div className="row-cost">{formatCompactCurrency(a.cost, currency)}</div>
<div className="row-turns">{a.turns}</div>
<div className="row-oneshot">
{a.oneShotRate == null ? '—' : `${Math.round(a.oneShotRate * 100)}%`}
</div>
</div>
</div>
))}
</section>
))}
</section>
)}
{payload.optimize.findingCount > 0 && (
<section className="findings">

View file

@ -157,6 +157,23 @@ html, body, #root {
.empty { color: var(--text-tertiary); font-size: 10.5px; padding: var(--spacing-md) 0; }
.empty-state {
padding: var(--spacing-md) var(--spacing-lg) var(--spacing-lg);
color: var(--text-secondary);
font-size: 11.5px;
line-height: 1.55;
}
.empty-state p { margin: 0 0 8px 0; }
.empty-state ul { margin: 0 0 10px 0; padding-left: 18px; }
.empty-state li { margin-bottom: 2px; }
.empty-state code {
font-family: var(--font-mono);
font-size: 10.5px;
background: rgba(255, 255, 255, 0.04);
padding: 1px 4px;
border-radius: 3px;
}
/* ---- findings ---- */
.findings { padding: 0 var(--spacing-lg) var(--spacing-sm); }
.findings-cta {