Desktop app shell (apps/desktop): Tauri 2 + React 19 around the storebound sidecar

- Rust host: spawns storebound serve --stdio with fixed args, hands over a per-session secret,
  mints confirm tokens only after a native dialog showing the summary and the dry run
- every app command re-checks window label and page origin; one capability (main window, app
  commands + event listen only, no plugin API in the webview); strict CSP; tests/security.rs pins it
- store keys picked in native dialogs, kept in the OS keychain, passed per call in memory only
- plugins: shell (Rust only), single-instance, deep-link storebound://, window-state, opener,
  dialog, notification, updater (placeholder endpoint and key until signing), process
- app-centric UI: Portfolio, App (Releases, Catalog, Listings, Health, Reviews, TestFlight),
  App folders, Accounts, Dependencies, Settings; light / dark with the storebound.dev tokens,
  reduced motion, home folder shown as ~
- macOS menu: Cmd+Q quits, Edit menu, Window menu keeps the main window; Linux DMABUF fix
- Bun workspace (npm files unchanged), ci runs desktop typecheck, build, cargo test and clippy;
  scrub covers home paths; docs/desktop.md (protocol, safety, dependency sources and licenses)
This commit is contained in:
CRTkafa 2026-10-11 01:56:47 +08:00
parent ab13d95898
commit 61a1e832a0
60 changed files with 9355 additions and 7 deletions

View file

@ -0,0 +1,69 @@
import { useCallback, useEffect, useState } from 'react';
import { api, errorText, type ToolStatus } from '../api';
import type { Data } from '../App';
import { shortPath } from '../paths';
/** Status tag of one tool. */
export function toolState(t: ToolStatus): { text: string; tone: 'ok' | 'warn' | 'local' } {
if (!t.found) return { text: 'missing', tone: 'warn' };
return t.source === 'app' ? { text: 'app copy', tone: 'local' } : { text: 'installed', tone: 'ok' };
}
/**
* Local tools storebound uses for screenshots, previews, icons and captures. ffmpeg and Chrome for Testing will be
* downloadable into the app's data folder (checksum or signature verified, never system-wide, never bundled);
* Xcode and the Android SDK are link-out only. Sources and licenses: docs/desktop.md.
*/
export function Dependencies({ data }: { data: Data }) {
const [tools, setTools] = useState<ToolStatus[] | null>(data.sidecar?.tools?.tools ?? null);
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const refresh = useCallback(async () => {
setBusy(true); setError(null);
try { setTools((await api.call<{ tools: ToolStatus[] }>('system.tools')).tools); } catch (e) { setError(errorText(e)); } finally { setBusy(false); }
}, []);
useEffect(() => { if (!tools) refresh(); }, [tools, refresh]);
const open = (url: string) => api.openExternal(url).catch(e => setError(errorText(e)));
return (
<>
<header className="head">
<div>
<h1>Dependencies</h1>
<p>Local tools for screenshots, app previews, icons and simulator or emulator captures. Store work (listings, products, releases) needs none of them.</p>
</div>
<div className="actions"><button className="btn" onClick={refresh} disabled={busy}>{busy ? 'Checking' : 'Check again'}</button></div>
</header>
<div className="stack">
{error && <div className="notice notice-bad" role="alert">{error}</div>}
<div className="card">
<table>
<thead><tr><th>Tool</th><th>Status</th><th>Version</th><th>Used for</th><th><span className="sr">Get it</span></th></tr></thead>
<tbody>
{(tools ?? []).map(t => {
const st = toolState(t);
return (
<tr key={t.id}>
<td><div className="name">{t.name}</div>{t.path && <div className="path selectable" title={t.path}>{shortPath(t.path, data.home)}</div>}</td>
<td><span className={`tag tag-${st.tone}`}>{st.text}</span></td>
<td className="selectable">{t.version ?? ''}</td>
<td className="muted">{t.usedFor}</td>
<td style={{ textAlign: 'right' }}>
{t.install.kind === 'download'
? <button className="btn" disabled title={`${t.install.note} One-click download comes with the next update.`}>Download</button>
: <button className="btn btn-quiet" onClick={() => open(t.install.url)} title={t.install.note}>Get it</button>}
</td>
</tr>
);
})}
{!tools && <tr><td colSpan={5} className="muted">Checking...</td></tr>}
</tbody>
</table>
</div>
<div className="notice notice-info">
ffmpeg and Chrome for Testing will download into this app's data folder only, verified before use, and are never part of the installer. Xcode and the Android SDK come from Apple and Google.
</div>
</div>
</>
);
}