Polish for shipping: robust auto-detect, empty state, real icons, end-user README

- cli_usage::default_command now enumerates WSL distros and probes each for
  claude before falling back; no more hardcoded -d Ubuntu.
- New autodetect_claude_command Tauri command + IPC binding so the UI knows
  whether claude is reachable.
- App.svelte: clear 'Claude Code not found' empty state with install link.
- Real icons: scripts/make-icon.py generates a 1024x1024 source.png; runtime
  produces 32/128/256 PNGs and a multi-resolution .ico. README in icons/
  explains how to regen.
- README rewritten for friends: install / requirements / troubleshooting on
  top; build-from-source moved to bottom.
This commit is contained in:
megaproxy 2026-05-09 14:25:24 +01:00
parent 0a960dae2d
commit 9be856d37c
14 changed files with 321 additions and 128 deletions

View file

@ -7,6 +7,8 @@
getCliUsage,
onCliUsageUpdated,
refreshCliUsage,
autodetectClaudeCommand,
getSettings,
} from "../ipc";
import TitleBar from "./TitleBar.svelte";
import BlockRing from "./BlockRing.svelte";
@ -18,6 +20,8 @@
let cliUsage = $state<CliUsage | null>(null);
let cliRefreshing = $state(false);
let showSettings = $state(false);
/** True when Claude Code can't be found anywhere on this machine. */
let claudeMissing = $state(false);
let unlisten1: (() => void) | null = null;
let unlisten2: (() => void) | null = null;
@ -40,9 +44,18 @@
console.error("listen failed", e);
}
// If we have nothing yet, fire a one-shot refresh so the widget is
// useful right away rather than waiting for the 5-min loop.
if (!cliUsage) {
// Probe whether claude is reachable at all.
try {
const settings = await getSettings();
const hasOverride = !!(settings.claude_command && settings.claude_command.trim());
const auto = await autodetectClaudeCommand();
claudeMissing = !hasOverride && !auto;
} catch (e) {
console.warn("autodetect probe failed", e);
}
// Trigger an initial refresh if we have nothing AND claude is reachable.
if (!cliUsage && !claudeMissing) {
void triggerRefresh();
}
});
@ -71,19 +84,38 @@
refreshing={cliRefreshing}
/>
<BlockRing bar={cliUsage?.session ?? null} />
{#if claudeMissing}
<div class="empty-state">
<div class="title">Claude Code not found</div>
<p>
This widget reads your subscription usage by running
<code>claude /usage</code>. Install Claude Code first, then sign in.
</p>
<p>
<a href="https://docs.claude.com/en/docs/claude-code" target="_blank" rel="noopener">
Install Claude Code →
</a>
</p>
<p class="hint">
Already installed? Open Settings and set <em>claude command</em>
(e.g. <code>wsl.exe -d Ubuntu bash -lc claude</code>).
</p>
</div>
{:else}
<BlockRing bar={cliUsage?.session ?? null} />
<ModelStack
breakdown={snap?.block?.by_family ?? snap?.weekly.by_family ?? { opus: 0, sonnet: 0, haiku: 0, other: 0 }}
/>
<ModelStack
breakdown={snap?.block?.by_family ?? snap?.weekly.by_family ?? { opus: 0, sonnet: 0, haiku: 0, other: 0 }}
/>
<WeeklyBar
weekAll={cliUsage?.week_all ?? null}
weekSonnet={cliUsage?.week_sonnet ?? null}
/>
<WeeklyBar
weekAll={cliUsage?.week_all ?? null}
weekSonnet={cliUsage?.week_sonnet ?? null}
/>
{#if cliRefreshing && !cliUsage}
<div class="loading">Reading /usage…</div>
{#if cliRefreshing && !cliUsage}
<div class="loading">Reading /usage…</div>
{/if}
{/if}
{#if showSettings}
@ -98,4 +130,30 @@
font-size: 12px;
text-align: center;
}
.empty-state {
flex: 1 1 0;
display: flex;
flex-direction: column;
justify-content: center;
padding: 12px var(--pad);
gap: 6px;
color: var(--fg);
font-size: 12px;
}
.empty-state .title {
color: var(--fg);
font-weight: 600;
text-align: center;
margin-bottom: 4px;
}
.empty-state p { margin: 0; line-height: 1.4; }
.empty-state .hint { color: var(--fg-dim); font-size: 11px; }
.empty-state a { color: var(--accent); text-decoration: none; }
.empty-state a:hover { text-decoration: underline; }
.empty-state code {
background: var(--bg-card);
padding: 1px 4px;
border-radius: 3px;
font-size: 11px;
}
</style>

View file

@ -19,6 +19,8 @@ export const quitApp = (): Promise<void> => invoke("quit_app");
export const detectPlanTier = (): Promise<TierInfo> => invoke("detect_plan_tier");
export const getCliUsage = (): Promise<CliUsage | null> => invoke("get_cli_usage");
export const refreshCliUsage = (): Promise<CliUsage> => invoke("refresh_cli_usage");
export const autodetectClaudeCommand = (): Promise<string[] | null> =>
invoke("autodetect_claude_command");
export const onUsageUpdated = (
cb: (snap: UsageSnapshot) => void,