Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Tests live alongside the code (`#[cfg(test)] mod tests`), plus `toolpath-cli` ha
- `toolpath-pi`: ~88 unit tests (types, paths, error, reader, io, provider)
- `toolpath-dot`: 30 unit + 2 doc tests (render, visual conventions, escaping)
- `toolpath-cli`: 126 unit + 24 integration tests (all commands, track sessions, merge, validate, roundtrip, render-md snapshots)
- `toolpath-desktop`: 13 unit tests (IPC command modules — source listing, derive validation, export round-trip, upload stub, keychain input checks)
- `toolpath-desktop`: 17 unit tests (IPC command modules — source listing, derive validation, export round-trip, upload stub, keychain input checks; tray activity-window bucketing, stats-snapshot smoke, session-id/basename helpers)

Validate example documents: `for f in examples/*.json; do cargo run -p toolpath-cli -- validate --input "$f"; done`

Expand All @@ -138,6 +138,10 @@ Layout:

Tauri dev loop: `cargo tauri dev` spawns `bun --cwd frontend run dev` (Vite on `http://localhost:1420`), then runs the Rust binary against that URL. Frontend edits hot-reload via Vite HMR without restarting Rust; Rust edits trigger `cargo run` to restart. Production: `cargo tauri build` runs `bun --cwd frontend run build` first, bundling to `frontend/dist/`.

Menu-bar mode: the app runs as a normal GUI app (Dock icon + app-switcher entry) *and* installs a tray icon — the tray is an accessory, not a replacement for the main window. Accessory activation policy was tried and reverted because macOS tiling window managers (yabai, Amethyst) stop managing accessory windows. A tray icon is installed in `src/tray.rs`; a background thread polls every 30s across `toolpath-claude`, `-gemini`, `-codex`, `-opencode`, and `-pi`, classifies sessions as *active* (last activity in the last 2 min) or *recent* (last 24h), updates the tray title (`● N`), and emits a `tray:stats` event. The popover is a second Tauri window (`label = "popover"`, undecorated, hidden by default) with its own Vite entry (`frontend/popover.html` → `src/popover.ts` → `routes/Popover.svelte`); left-clicking the tray toggles it via `tauri-plugin-positioner`. For an on-demand snapshot (no waiting for the next poll) the popover invokes the `tray_stats_now` IPC command.

Opening a trace from the popover: clicking a recent-session row invokes `tray_open_trace { provider, project, session_id }`. The Rust side calls back into the existing `derive_claude` / `derive_pi` commands, shows the main window, and emits a `trace:opened` event to the main window with the derived `{ doc, source, filename }`. `app.svelte` listens for it and dispatches `DeriveSucceeded`, which routes to the preview. Only `claude` and `pi` have derive commands today — rows for `gemini`, `codex`, `opencode` still appear in the list (so users can see activity) but are rendered disabled.

Streaming pattern (Claude project/session lists): Rust command spawns a thread that emits `claude:project`, `claude:session`, `claude:projects-done`, `claude:sessions-done` events. The Svelte component subscribes with `$effect(() => { listen(...) ... return unlisten; })` — Svelte tears down listeners automatically when the effect's deps change or the component unmounts.

Package manager for the frontend is `bun` (installed at `~/.bun/bin/bun`). `bun install` to set up, `bun run check` for `svelte-check`, `bun run build` for a production Vite build. Never commit `node_modules/` or `dist/` — both are ignored.
Expand Down
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/toolpath-desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ tauri-build = { version = "2", features = [] }
[dependencies]
toolpath = { workspace = true }
toolpath-claude = { workspace = true, features = ["watcher"] }
toolpath-codex = { workspace = true }
toolpath-gemini = { workspace = true }
toolpath-git = { workspace = true }
toolpath-github = { workspace = true }
toolpath-opencode = { workspace = true }
toolpath-pi = { workspace = true }

tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-dialog = "2"
tauri-plugin-opener = "2"
tauri-plugin-positioner = { version = "2", features = ["tray-icon"] }

serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
8 changes: 8 additions & 0 deletions crates/toolpath-desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ intended for people who won't open a terminal to run the `path` CLI.
3. **Export** — save the document as a local `.path.json` file, **or** upload
it to Pathbase. The Pathbase upload is stubbed in v0.1 and logs a mock
response; the real API will be wired up in a follow-up.
4. **Quick View (menu bar)** — a tray icon that sits alongside the regular
Dock-icon app. A background thread polls every 30s across all five agent
providers (`toolpath-claude`, `-gemini`, `-codex`, `-opencode`, `-pi`)
and updates the tray title with an activity count. Left-click opens a
small popover listing recent sessions; the menu has an "Open Toolpath"
item that brings up the main window, and clicking a recent session in
the popover opens its trace directly in the preview. See `src/tray.rs`
and `frontend/src/routes/Popover.svelte`.

## Architecture

Expand Down
11 changes: 9 additions & 2 deletions crates/toolpath-desktop/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capabilities required by the Toolpath desktop app.",
"windows": ["main"],
"windows": ["main", "popover"],
"permissions": [
"core:default",
"dialog:default",
"opener:default"
"opener:default",
"positioner:default",
"core:window:allow-show",
"core:window:allow-hide",
"core:window:allow-set-focus",
"core:window:allow-close",
"core:window:allow-is-visible",
"core:window:allow-start-dragging"
]
}
12 changes: 12 additions & 0 deletions crates/toolpath-desktop/frontend/popover.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Toolpath Quick View</title>
</head>
<body>
<div id="popover"></div>
<script type="module" src="/src/popover.ts"></script>
</body>
</html>
26 changes: 25 additions & 1 deletion crates/toolpath-desktop/frontend/src/app.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script lang="ts">
import { store } from "./lib/store.svelte";
import { listen } from "./lib/ipc";
import type { UnlistenFn } from "@tauri-apps/api/event";
import Home from "./routes/Home.svelte";
import BrowseAgents from "./routes/BrowseAgents.svelte";
import BrowseClaude from "./routes/BrowseClaude.svelte";
Expand All @@ -8,7 +10,7 @@
import BrowseGithub from "./routes/BrowseGithub.svelte";
import Preview from "./routes/Preview.svelte";
import Result from "./routes/Result.svelte";
import type { Route } from "./lib/types";
import type { Document, Route } from "./lib/types";

const notInTauri =
typeof window !== "undefined" &&
Expand Down Expand Up @@ -53,6 +55,28 @@
function toggleTheme() {
theme = theme === "dark" ? "light" : "dark";
}

// Quick View menu-bar → main window: when the user clicks a recent
// session in the tray popover, the Rust side derives the doc and emits
// `trace:opened` here. We route it through the standard DeriveSucceeded
// flow so the reducer navigates to the preview route.
interface TraceOpenedPayload {
doc: Document;
source: string;
filename: string;
}
$effect(() => {
let unlisten: UnlistenFn | undefined;
listen<TraceOpenedPayload>("trace:opened", (payload) => {
store.dispatch({
t: "DeriveSucceeded",
doc: payload.doc,
source: payload.source,
filename: payload.filename,
});
}).then((fn) => (unlisten = fn));
return () => unlisten?.();
});
</script>

<div class="backdrop"></div>
Expand Down
7 changes: 7 additions & 0 deletions crates/toolpath-desktop/frontend/src/popover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { mount } from "svelte";
import Popover from "./routes/Popover.svelte";
import "./styles.css";

const target = document.getElementById("popover");
if (!target) throw new Error("#popover not found");
mount(Popover, { target });
Loading
Loading