Canopydocs v0.4.7

Commands and events

The frontend talks to the Rust backend through Tauri's invoke, and receives pushed state through events. This is the whole surface, which is useful when you're reading the code or extending it. The typed wrappers live in src/ipc.ts, the handlers in src-tauri/src/commands.rs.

What the UI receives#

RepoNode      { repoId, name, path, worktrees: WorktreeNode[] }
WorktreeNode  { wtKey, branch, path, isMain, git | null, dbName | null, services: ServiceNode[] }
ServiceNode   { svcKey, serviceId, name, kind, port | null, status }
GitMeta       { ahead, behind, dirty, lastCommitTs, lastCommitMsg }

wtKey  = the worktree's absolute path
svcKey = `${wtKey}::${serviceId}`
status ∈ stopped | starting | running | stopping | error
kind   ∈ web | server | worker

Commands#

Tree and settings#

CommandSignatureNotes
get_tree() → RepoNode[]Full hydrate.
refresh(wtKey?) → voidRescan everything, or one worktree.
get_settings() → Settings
save_settings(newSettings) → void
add_repo(path) → RepoCfgRegisters a repository.
detect_repo(path) → RepoDetectionName, branch, origin, stack, package.json scripts.
remove_repo(repoId) → voidStops tracking. Touches nothing on disk.
get_repo_config(repoId) → RepoConfigFileReads .worktreemanager.json.
save_repo_config(repoId, provision, setup) → voidWrites it.
save_text_file(path, contents) → voidUsed by config export.

Worktrees#

CommandSignatureNotes
create_worktree({repoId, branch, base?, createBranch}) → pathadd → submodules → provision → setup.
run_worktree_setup(wtKey) → voidRe-provision and re-run setup.
remove_worktree(wtKey, deleteBranch, dropDb) → void
remove_worktrees(wtKeys[], deleteBranch, dropDb) → voidThe multi-select path.
list_prunable_worktrees() → PrunableWorktree[]Folders deleted on disk.
prune_worktrees(items[]) → voidPer item: branch and database choices.
switch_worktree_branch(wtKey, branch, create, base?) → voidIn-place switch.
worktree_dirty_report(wtKey) → {dirty, details[], total}Up to ten paths, submodules included.
worktree_status(wtKey) → StatusEntry[]Resolved git status --porcelain rows.
worktree_commit(wtKey, message, addUntracked) → void
worktree_stash(wtKey, name?, includeUntracked) → summary
worktree_discard(wtKey, cleanUntracked) → void

Git and submodules#

CommandSignatureNotes
git_pull(wtKey) → summary--ff-only, then advances submodules.
submodule_status(wtKey) → SubmoduleStatus[]branch, sha, dirty, ahead-of-pin.
pull_submodule(wtKey, path) → summary
switch_submodule_branch(wtKey, path, branch) → void
list_submodule_branches(wtKey, path) → Branches
fetch_submodules(wtKey) → count
sync_submodules(wtKey) → summaryRe-pin to the recorded commits.
list_branches(repoId) → Brancheslocal, remote, tags.
fetch_branches(repoId) → BranchesFetch, then list.

Services#

CommandSignature
service_start / service_stop / service_restart(svcKey) → void
worktree_start_all / worktree_stop_all(wtKey) → void
set_service_port(svcKey, port) → void, validated 1024–65535
get_logs(svcKey) → LogLine[], the ring buffer snapshot

Databases#

CommandSignature
list_databases(wtKey) → string[]
current_database`(wtKey) → string \null`
switch_database(wtKey, dbName) → void
snapshot_database(wtKey, name) → void
export_database(wtKey, filePath) → void
restore_database(wtKey, filePath) → void
reset_db(wtKey) → void
run_migration(wtKey) → void

Terminals and agents#

CommandSignatureNotes
terminal_open(id, cwd, cols, rows, command?) → voidIdempotent per id.
terminal_write(id, data) → void
terminal_resize(id, cols, rows) → void
terminal_get_buffer`(id) → {buffer, seq} \null`Race-free rehydrate.
terminal_close(id) → void
write_worktree_context(wtPath, contents) → voidWrites .canopy/context.md and a self-ignoring .gitignore.
resolve_agent_command(wtKey) → string

Shell-outs and windows#

CommandSignature
run_custom_command(wtKey, command) → void
open_in_editor(wtKey) → void
open_file_in_editor(wtKey, path) → void
reveal_in_finder(wtKey) → void
open_terminal(wtKey) → void
open_port(port) → void
show_main_window() → void
quit_app() → void

Events#

Pushed from Rust to whichever windows are listening. High-volume events are filtered to the windows that consume them, and suppressed entirely while every window is hidden.

EventPayloadMeaning
tree:changedRepoNode[]A structural change: repo, worktree, service or settings.
service:status{svcKey, status, startedAt?, exitCode?}A service's lifecycle moved.
service:log{svcKey, lines[]}Batched log lines, every ~200 ms.
service:stats{entries: [{svcKey, cpu, memMb, uptimeSec}]}The 2-second stats poll.
worktree:git{wtKey, ahead, behind, dirty, lastCommitTs, lastCommitMsg}Git metadata refreshed.
reset:status{wtKey, state, message?}Database reset progress.
worktree:op{wtKey, op, state, detail}create / remove / setup / migrate / snapshot progress, carrying the [k/n]: step markers.
terminal:data{id, data (base64), seq}Raw PTY bytes.
terminal:exit{id}A session's process ended.
tray:new-worktree · tray:overview · tray:settingsEmitted by the popover for the main window to act on.

Errors#

Every command rejects with a structured error:

{ code: "git" | "db" | "setup" | "process" | "terminal" | "config"
       | "not_found" | "invalid_input" | "conflict" | "internal",
  message: string }

The frontend reads it with errText() and errCode() instead of String(e), so a plugin's plain-string rejection and a backend error both come out useful.

Detecting the backend#

hasBackend() checks for __TAURI_INTERNALS__ on window. It's false in a plain browser, which is what enables mock mode, and why features that need the backend say so with a toast instead of failing silently.

Documentation for Canopy 0.4.7. Controls marked coming soon are present in the interface but have no implementation behind them yet.