All IPC communication uses Electron's ipcRenderer.invoke() / ipcMain.handle() pattern.
Every call is asynchronous and returns a Promise.
The renderer accesses these channels via window.electronAPI.<method>() (exposed by the preload script).
| Channel | Preload method | Direction |
|---|---|---|
project:create |
projectCreate() |
Renderer → Main |
project:delete |
projectDelete() |
Renderer → Main |
project:list |
projectList() |
Renderer → Main |
project:tree |
projectTree() |
Renderer → Main |
project:rename |
projectRename() |
Renderer → Main |
project:archive |
projectArchive() |
Renderer → Main |
project:unarchive |
projectUnarchive() |
Renderer → Main |
project:has-changes |
projectHasChanges() |
Renderer → Main |
project:storage-stats |
projectStorageStats() |
Renderer → Main |
project:get-tags |
projectGetTags() |
Renderer → Main |
project:set-tags |
projectSetTags() |
Renderer → Main |
milestone:create-initial |
milestoneCreateInitial() |
Renderer → Main |
milestone:create |
milestoneCreate() |
Renderer → Main |
milestone:restore |
milestoneRestore() |
Renderer → Main |
milestone:delete |
milestoneDelete() |
Renderer → Main |
milestone:rename |
milestoneRename() |
Renderer → Main |
milestone:set-tags |
milestoneSetTags() |
Renderer → Main |
milestone:set-description |
milestoneSetDescription() |
Renderer → Main |
milestone:storage-size |
milestoneStorageSize() |
Renderer → Main |
milestone:tracked-files |
milestoneTrackedFiles() |
Renderer → Main |
milestone:export-zip |
milestoneExportZip() |
Renderer → Main |
autowatch:start |
autoWatchStart() |
Renderer → Main |
autowatch:stop |
autoWatchStop() |
Renderer → Main |
autowatch:status |
autoWatchStatus() |
Renderer → Main |
autowatch:milestone-created |
onAutoWatchMilestoneCreated() |
Main → Renderer |
settings:get |
settingsGet() |
Renderer → Main |
settings:set |
settingsSet() |
Renderer → Main |
shell:open-external |
openExternal() |
Renderer → Main |
blacklist:get |
blacklistGet() |
Renderer → Main |
blacklist:set |
blacklistSet() |
Renderer → Main |
Create a new Bonsai project. Scaffolds the .app_data/ folder and registers the project in the global registry.
const result = await window.electronAPI.projectCreate(projectPath, name);| Name | Type | Default | Description |
|---|---|---|---|
projectPath |
string |
— | Absolute path to the project root directory |
name |
string |
— | Human-readable project name |
{ id: string; status: 'success' | 'error' }Delete a Bonsai project. Removes .app_data/, .git/, .gitignore and unregisters from the global list.
const result = await window.electronAPI.projectDelete(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster"]
// Response
{
"status": "success"
}List all registered Bonsai projects with summary information.
const projects = await window.electronAPI.projectList();None.
Array<{
id: string;
name: string;
projectPath: string;
createdAt: string; // ISO 8601 timestamp
lastMilestoneAt: string | null; // ISO 8601 or null if no milestones
milestoneCount: number;
lastMilestoneMessage: string | null; // Message of the most recent milestone
}>// Response
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "City Poster Design",
"projectPath": "/home/user/city-poster",
"createdAt": "2026-03-01T10:00:00.000Z",
"lastMilestoneAt": "2026-03-05T14:30:00.000Z",
"milestoneCount": 5,
"lastMilestoneMessage": "Refined color grading"
},
{
"id": "f9e8d7c6-b5a4-3210-fedc-ba0987654321",
"name": "Logo Redesign",
"projectPath": "/home/user/logo-redesign",
"createdAt": "2026-02-15T08:00:00.000Z",
"lastMilestoneAt": "2026-02-28T16:45:00.000Z",
"milestoneCount": 12
}
]Note: If a project's folder was deleted externally, its entry will still appear with
name: "(unavailable)", emptycreatedAt, andmilestoneCount: 0.
Get the full milestone tree (DAG), branch list, and flat milestone array for a project. This is the primary data source for rendering the visual version-control graph.
const data = await window.electronAPI.projectTree(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{
tree: TreeNode[]; // Root node(s) of the milestone DAG
branches: string[]; // All Git branch names in the project
milestones: MilestoneRecord[]; // Flat list of every milestone
activeMilestoneId: string | null; // Milestone matching current HEAD (null if none)
}TreeNode shape:
{
milestoneId: string;
message: string;
commitHash: string;
branch: string;
createdAt: string; // ISO 8601
children: TreeNode[]; // Nested child milestones
tags?: string[]; // Optional semantic tags (e.g. "release", "wip")
description?: string; // Optional longer description
}MilestoneRecord shape:
{
milestoneId: string;
message: string;
commitHash: string;
branch: string;
parentMilestoneId: string | null;
patchFiles: string[]; // Relative paths inside .app_data/patches/
createdAt: string; // ISO 8601
tags?: string[]; // Optional semantic tags
description?: string; // Optional longer description
}// Request args
["/home/user/city-poster"]
// Response
{
"tree": [
{
"milestoneId": "aaa-111",
"message": "Initial canvas setup",
"commitHash": "e3b0c44",
"branch": "main",
"createdAt": "2026-03-01T10:00:00.000Z",
"children": [
{
"milestoneId": "bbb-222",
"message": "Added background layer",
"commitHash": "a1b2c3d",
"branch": "main",
"createdAt": "2026-03-02T12:00:00.000Z",
"children": [
{
"milestoneId": "ccc-333",
"message": "Refined color grading",
"commitHash": "d4e5f6a",
"branch": "main",
"createdAt": "2026-03-03T09:00:00.000Z",
"children": []
},
{
"milestoneId": "ddd-444",
"message": "Alternative: dark theme",
"commitHash": "7b8c9d0",
"branch": "branch-ddd-444",
"createdAt": "2026-03-03T11:00:00.000Z",
"children": []
}
]
}
]
}
],
"branches": ["main", "branch-ddd-444"],
"milestones": [
{
"milestoneId": "aaa-111",
"message": "Initial canvas setup",
"commitHash": "e3b0c44",
"branch": "main",
"parentMilestoneId": null,
"patchFiles": [],
"createdAt": "2026-03-01T10:00:00.000Z"
},
{
"milestoneId": "bbb-222",
"message": "Added background layer",
"commitHash": "a1b2c3d",
"branch": "main",
"parentMilestoneId": "aaa-111",
"patchFiles": ["patches/bbb-222/city-poster.psd.patch"],
"createdAt": "2026-03-02T12:00:00.000Z"
},
{
"milestoneId": "ccc-333",
"message": "Refined color grading",
"commitHash": "d4e5f6a",
"branch": "main",
"parentMilestoneId": "bbb-222",
"patchFiles": ["patches/ccc-333/city-poster.psd.patch"],
"createdAt": "2026-03-03T09:00:00.000Z"
},
{
"milestoneId": "ddd-444",
"message": "Alternative: dark theme",
"commitHash": "7b8c9d0",
"branch": "branch-ddd-444",
"parentMilestoneId": "bbb-222",
"patchFiles": ["patches/ddd-444/city-poster.psd.patch"],
"createdAt": "2026-03-03T11:00:00.000Z"
}
],
"activeMilestoneId": "ccc-333"
}Create the first milestone for a project. Copies binary files to the base folder, initialises Git, builds .gitignore, and commits metadata.
const result = await window.electronAPI.milestoneCreateInitial(projectPath, message, description?);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory (binary files are scanned from here) |
message |
string |
Description for the initial milestone |
description |
string? |
Optional longer description for the milestone |
{ milestoneId: string }// Request args
["/home/user/city-poster", "Initial canvas setup"]
// Response
{
"milestoneId": "aaa-111-bbb-222-ccc"
}Create a subsequent milestone. Runs xdelta3 to compute binary diffs against the previous state, stores patches, and commits to Git. If the current HEAD already has children, a new branch is created automatically.
const result = await window.electronAPI.milestoneCreate(projectPath, message, description?);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
message |
string |
Description for this milestone |
description |
string? |
Optional longer description for the milestone |
{ milestoneId: string }// Request args
["/home/user/city-poster", "Added skyline silhouette layer"]
// Response
{
"milestoneId": "bbb-222-ccc-333-ddd"
}Restore the project's working directory to the state at a specific milestone. Checks out the Git commit and reconstructs binary files by sequentially applying xdelta3 patches from the base.
const result = await window.electronAPI.milestoneRestore(projectPath, milestoneId);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "aaa-111-bbb-222-ccc"]
// Response
{
"status": "success"
}Delete a milestone. Removes its patch files and metadata entry. The Git commit is not rewritten — it stays in the DAG but becomes unreachable if the branch is pruned.
Constraint: A milestone that has children (other milestones that depend on it) cannot be deleted. You must delete the leaf milestones first.
const result = await window.electronAPI.milestoneDelete(projectPath, milestoneId);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the milestone to delete |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "ccc-333-ddd-444-eee"]
// Response
{
"status": "success"
}Rename an existing project. Updates the entry in the global registry without touching the project's files or Git history.
const result = await window.electronAPI.projectRename(projectPath, newName);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
newName |
string |
New human-readable project name |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "City Poster 2026"]
// Response
{
"status": "success"
}Archive a project. Sets the archived flag in projects.json and stops auto-watch if active. Archived projects still appear in project:list with archived: true but are displayed in a separate collapsed section in the UI.
const result = await window.electronAPI.projectArchive(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster"]
// Response
{
"status": "success"
}Unarchive a previously archived project. Clears the archived flag in projects.json, restoring the project to the active list.
const result = await window.electronAPI.projectUnarchive(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster"]
// Response
{
"status": "success"
}Check whether the project's working directory has unsaved changes relative to the currently active milestone. Used to warn the user before a restore or branch operation would discard work.
const result = await window.electronAPI.projectHasChanges(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ hasChanges: boolean }// Request args
["/home/user/city-poster"]
// Response
{
"hasChanges": true
}Return aggregate storage statistics for a project: total size of base snapshots, total size of patch files, and number of milestones.
const result = await window.electronAPI.projectStorageStats(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{
totalBase: number; // Bytes used by .app_data/base/
totalPatches: number; // Bytes used by .app_data/patches/
milestoneCount: number; // Total number of milestones
}// Request args
["/home/user/city-poster"]
// Response
{
"totalBase": 52428800,
"totalPatches": 8388608,
"milestoneCount": 12
}Rename an existing milestone. Updates the message stored in the project registry.
const result = await window.electronAPI.milestoneRename(projectPath, milestoneId, newMessage);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
newMessage |
string |
The new milestone name / description |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "bbb-222", "First background pass (revised)"]
// Response
{
"status": "success"
}Replace the tag list for a milestone. Tags are labels from the project's own custom tag definitions (see project:get-tags). Pass an empty array to clear all tags.
const result = await window.electronAPI.milestoneSetTags(projectPath, milestoneId, tags);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
tags |
string[] |
Replacement tag list — labels must exist in the project's customTags (pass [] to clear all tags) |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "ccc-333", ["release", "backup"]]
// Response
{
"status": "success"
}Get the total on-disk size (in bytes) of the patch files stored for a specific milestone.
const result = await window.electronAPI.milestoneStorageSize(projectPath, milestoneId);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
{ bytes: number }// Request args
["/home/user/city-poster", "bbb-222"]
// Response
{
"bytes": 2097152
}List the relative file paths that are tracked (stored) by a specific milestone. For the initial milestone these are the base copies; for subsequent milestones these are the files for which a patch was computed.
const result = await window.electronAPI.milestoneTrackedFiles(projectPath, milestoneId);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
{ files: string[] } // Relative paths from the project root// Request args
["/home/user/city-poster", "bbb-222"]
// Response
{
"files": ["city-poster.psd", "reference/skyline.png"]
}Export the full reconstructed file state of a milestone as a .zip archive. Opens a native Save As dialog for the user to choose the destination. Returns null if the user cancels the dialog.
const result = await window.electronAPI.milestoneExportZip(projectPath, milestoneId);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the milestone to export |
{ status: 'success' | 'cancelled' | 'error'; outputPath?: string }// Request args
["/home/user/city-poster", "ccc-333"]
// Response (user chose a path)
{
"status": "success",
"outputPath": "/home/user/Downloads/city-poster-ccc-333.zip"
}
// Response (user cancelled)
{
"status": "cancelled"
}Returned by project:list. One entry per registered project.
interface ProjectSummary {
id: string; // UUID
name: string; // Human-readable name
projectPath: string; // Absolute filesystem path
createdAt: string; // ISO 8601 timestamp
lastMilestoneAt: string | null; // ISO 8601 or null
milestoneCount: number; // Total milestones in the project
lastMilestoneMessage: string | null; // Message of the most recent milestone
}A custom tag belonging to a project or the global default-tags list.
interface TagDefinition {
label: string; // Short display name (e.g. "release", "wip")
color: string; // Hex color string (e.g. "#22c55e")
}A node in the milestone DAG (used by project:tree).
interface TreeNode {
milestoneId: string;
message: string;
commitHash: string;
branch: string;
createdAt: string;
children: TreeNode[];
tags?: string[]; // Labels from the project's customTags
description?: string; // Optional longer description
}Full milestone data (stored in global_registry.json).
interface MilestoneRecord {
milestoneId: string;
message: string;
commitHash: string;
branch: string;
parentMilestoneId: string | null;
patchFiles: string[];
createdAt: string;
tags?: string[]; // Labels from the project's customTags
description?: string; // Optional longer description
}Set or update the description for an existing milestone.
const result = await window.electronAPI.milestoneSetDescription(projectPath, milestoneId, description);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
milestoneId |
string |
UUID of the target milestone |
description |
string |
The new description text (pass "" to clear) |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", "bbb-222", "Added the skyline silhouette layer with gradient masking"]
// Response
{
"status": "success"
}Retrieve a single app setting value by key. Settings are persisted in ~/.config/bonsai/settings.json (or platform equivalent).
const value = await window.electronAPI.settingsGet(key);| Name | Type | Description |
|---|---|---|
key |
string |
The setting key to read (e.g. "launchToTray") |
The value of the setting, or undefined if not set.
// Request args
["launchToTray"]
// Response
falseUpdate a single app setting and persist it to disk. Changes to launchToTray take effect immediately (creating or destroying the system tray icon).
const result = await window.electronAPI.settingsSet(key, value);| Name | Type | Description |
|---|---|---|
key |
string |
The setting key to update (e.g. "launchToTray") |
value |
unknown |
The new value for the setting |
{ status: 'success' }// Request args
["launchToTray", true]
// Response
{
"status": "success"
}| Key | Type | Default | Description |
|---|---|---|---|
branchColorsEnabled |
boolean |
true |
Color-code branches on the timeline canvas using the 8-color palette |
minimapEnabled |
boolean |
true |
Show the minimap overview panel on the timeline canvas |
autoWatchDebounceMs |
number |
10000 |
Milliseconds to wait after the last file change before auto-creating a milestone (min 1000) |
milestoneNameTemplate |
string |
"" |
Default milestone name template. Supports {{n}} (milestone count) and {{date}} (locale date) placeholders |
canvasDirection |
string |
"horizontal" |
Canvas layout direction: "horizontal" (Left → Right) or "vertical" (Top → Down) |
defaultTags |
TagDefinition[] |
[] |
Default set of custom tags copied into every newly created project. Each entry is { label: string; color: string } |
Open a URL in the user's default system browser. Only http:// and https:// URLs are permitted — other schemes are silently ignored.
await window.electronAPI.openExternal(url);| Name | Type | Description |
|---|---|---|
url |
string |
The URL to open. Must begin with http:// or https:// |
void
await window.electronAPI.openExternal('https://github.com/K3lvin4SY');Start watching a project folder for file changes. When a change is detected, Bonsai waits for the configured debounce interval (default 10 s, adjustable per project via settings:set → autoWatchDebounceMs) before automatically creating a milestone. This prevents corruption from rapid saves (e.g. an application writing multiple files at once). Changes to internal bookkeeping directories (.git, .app_data, .tmp, node_modules) are ignored.
The watcher is off by default and must be explicitly enabled per project.
const result = await window.electronAPI.autoWatchStart(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ status: 'success' | 'error'; error?: string }// Request args
["/home/user/city-poster"]
// Response
{
"status": "success"
}Stop watching a project folder for file changes. Any pending debounce timer is cancelled.
const result = await window.electronAPI.autoWatchStop(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster"]
// Response
{
"status": "success"
}Check whether auto-watch is currently active for a project.
const result = await window.electronAPI.autoWatchStatus(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
{ active: boolean }// Request args
["/home/user/city-poster"]
// Response
{
"active": true
}Pushed from the main process to all renderer windows whenever the auto-watch system creates a milestone. The renderer uses this to refresh the project tree in real time.
This is not an invoke/handle channel — it uses ipcMain → webContents.send() / ipcRenderer.on().
const cleanup = window.electronAPI.onAutoWatchMilestoneCreated(
(projectPath, milestoneId) => {
console.log(`Auto-save milestone ${milestoneId} created for ${projectPath}`);
}
);
// Call cleanup() to unsubscribe| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path of the project that was auto-saved |
milestoneId |
string |
UUID of the newly created milestone |
// Payload sent by main process
["/home/user/city-poster", "f47ac10b-58cc-4372-a567-0e02b2c3d479"]Retrieve the blacklist (list of ignored file/folder paths) for a project. Blacklisted items are completely excluded from Bonsai’s version tracking — no base copies, no xdelta3 patches, and they are added to .gitignore.
const items = await window.electronAPI.blacklistGet(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
string[] // Array of relative paths (e.g. ["renders", "archive/old-assets", "tmp-export.psd"])// Request args
["/home/user/city-poster"]
// Response
["renders", "archive/old-assets", "tmp-export.psd"]Replace the entire blacklist for a project. Persists the list in the project registry and regenerates .gitignore to include the blacklisted paths.
const result = await window.electronAPI.blacklistSet(projectPath, items);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
items |
string[] |
Array of relative paths to ignore (files and/or folders) |
{ status: 'success' | 'error' }// Request args
["/home/user/city-poster", ["renders", "archive/old-assets", "tmp-export.psd"]]
// Response
{
"status": "success"
}Get the list of custom tag definitions for a project. Tags are stored in the project's global_registry.json under customTags.
const tags = await window.electronAPI.projectGetTags(projectPath);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
Array<{ label: string; color: string }>// Request args
["/home/user/city-poster"]
// Response
[
{ "label": "release", "color": "#22c55e" },
{ "label": "wip", "color": "#f59e0b" },
{ "label": "draft", "color": "#a855f7" }
]Replace the entire custom tag list for a project. This is the single source of truth for which tags can be assigned to milestones in this project. Pass an empty array to clear all tags.
Note: Removing a tag definition here does not remove it from milestones that already reference it — it simply becomes an orphaned label with no color mapping.
const result = await window.electronAPI.projectSetTags(projectPath, tags);| Name | Type | Description |
|---|---|---|
projectPath |
string |
Absolute path to the project root directory |
tags |
Array<{ label: string; color: string }> |
Full replacement tag list |
{ status: 'success' | 'error' }// Request args
[
"/home/user/city-poster",
[
{ "label": "release", "color": "#22c55e" },
{ "label": "wip", "color": "#f59e0b" },
{ "label": "draft", "color": "#a855f7" }
]
]
// Response
{
"status": "success"
}