@takazudo/backend-bridge
Abstracts the backend communication layer so the frontend works with both the real Tauri backend and a mock backend for tests and Storybook. The adapter pattern allows swapping the backend without changing any UI code.
Architecture
Frontend Components
│
▼
getBackend() ←── initBackend(adapter)
│
├── TauriAdapter (native IPC + encrypted cloud workspace)
├── MockAdapter (in-memory, for tests & Storybook)
└── RestAdapter (cloud workspace or local HTTP/SSE dev fallback)Note
messages, pins, notes, and inbox are bridge-level domain names, not proof that same-named Tauri commands exist. In the shipped cloud-primary Tauri adapter, document operations execute against the encrypted workspace model. The matching Rust filesystem engine and HTTP/SSE routes are retained forpnpm dev:rest when no cloud workspace is armed; the renderer does not invokemessages_*, notes_*, or inbox_* filesystem commands. getProjectRoot()reports the compatibility root used by surviving local-only surfaces, not an editable cloud workspace directory.
Subpath Exports
// Main: types + init/get functions
import { initBackend, getBackend } from "@takazudo/backend-bridge";
import { defaultCloudSyncStatus, defaultAuthState } from "@takazudo/backend-bridge";
// Tauri adapter (subpath — not in main index.ts exports)
import { createTauriAdapter } from "@takazudo/backend-bridge/tauri-adapter";
// Mock adapter (for tests & Storybook)
import { createMockAdapter } from "@takazudo/backend-bridge";
// REST adapter (for browser dev with real backend)
import { createRestAdapter } from "@takazudo/backend-bridge";initBackend / getBackend
The singleton pattern ensures the backend is initialized once at app startup and accessed consistently everywhere.
function initBackend(adapter: BackendAPI): void;
function getBackend(): BackendAPI;Usage
// At app startup
import { initBackend } from "@takazudo/backend-bridge";
import { createTauriAdapter } from "@takazudo/backend-bridge/tauri-adapter";
initBackend(createTauriAdapter());
// In any component
import { getBackend } from "@takazudo/backend-bridge";
const backend = getBackend();
const messages = await backend.messages.list();BackendAPI Interface
The BackendAPI interface defines the complete contract between frontend and backend:
interface BackendAPI {
/** Static set of capabilities for this adapter. */
capabilities: Capabilities;
/** Return the runtime role: "root" (ROOT app) or "leaf" (generated LEAF app). */
appMode: () => Promise<AppRole>;
/** Low-level file I/O for arbitrary absolute paths (e.g. drag-and-drop import). */
files: {
readText: (sourcePath: string) => Promise<string>;
writeText: (sourcePath: string, content: string) => Promise<void>;
deleteFile: (path: string) => Promise<void>;
mkdir: (dir: string) => Promise<void>;
getUserSkillsDir: () => Promise<string>;
readDir: (path: string) => Promise<string[]>;
watchDir: (path: string, watcherId: string) => Promise<void>;
unwatchDir: (watcherId: string) => Promise<void>;
onSkillsChanged: (
watcherId: string,
callback: (filePath: string, kind: "added" | "updated" | "removed") => void,
) => () => void;
};
messages: {
list: () => Promise<MessageMeta[]>;
/** Light listing: frontmatter + fs metadata only — no body or snippet. */
listLight: () => Promise<MessageMeta[]>;
listWithBody: () => Promise<MessageWithBody[]>;
read: (filename: string) => Promise<string | null>;
write: (filename: string, content: string) => Promise<boolean>;
delete: (filename: string) => Promise<void>;
create: (name: string, content: string) => Promise<string>;
/** Create a message in a directory relative to the workspace root (e.g. "/archives/"). */
createInDir: (relDir: string, name: string, content: string) => Promise<string>;
rename: (oldFilename: string, newFilename: string) => Promise<string>;
onChanged: (callback: (filename?: string) => void) => () => void;
};
pins: {
list: (pinIndex: number) => Promise<PinEntry[]>;
/** Fetch immediate children of a directory within a pin (lazy expansion). */
listChildren: (pinIndex: number, dirPath: string) => Promise<PinEntry[]>;
read: (pinIndex: number, entryPath: string) => Promise<string | null>;
write: (pinIndex: number, entryPath: string, content: string) => Promise<boolean>;
delete: (pinIndex: number, entryPath: string) => Promise<boolean>;
watchFile: (pinIndex: number, entryPath: string) => Promise<boolean>;
unwatchFile: () => Promise<boolean>;
onFileChanged: (callback: (entryPath: string) => void) => () => void;
/** Create a new file in the pin directory; returns the relative filename. */
createFile: (pinIndex: number) => Promise<string>;
/**
* Write a new file only if the path does not already exist.
* Rejects with WriteNewFileError on collision or validation failure.
*/
writeNewFile: (pinIndex: number, filename: string, content: string) => Promise<WriteNewFileResult>;
};
/** W9.1 — frame pop-out / dock-back command surface. */
framePopout: {
popOut: (frameId: string, providerId: string, propsJson: string) => Promise<string>;
dock: (windowLabel: string) => Promise<void>;
onPopoutClosed: (callback: (frameId: string, windowLabel: string) => void) => () => void;
// Cross-window restore protocol (#1920):
requestRestore: (windowLabel: string) => Promise<void>;
onRestoreRequest: (callback: (windowLabel: string) => void) => () => void;
sendRestorePayload: (windowLabel: string, propsJson: string) => Promise<void>;
onRestorePayload: (callback: (windowLabel: string, propsJson: string) => void) => () => void;
sendRestoreAck: (windowLabel: string) => Promise<void>;
onRestoreAck: (callback: (windowLabel: string) => void) => () => void;
sendReturnRequest: (windowLabel: string, propsJson: string) => Promise<void>;
onReturnRequest: (callback: (windowLabel: string, propsJson: string) => void) => () => void;
};
inbox: {
read: (draftNumber: number) => Promise<string | null>;
write: (draftNumber: number, content: string) => Promise<boolean>;
delete: (draftNumber: number) => Promise<InboxDeleteResult | null>;
getActive: () => Promise<number>;
setActive: (draftNumber: number) => Promise<boolean>;
getCount: () => Promise<number>;
new: () => Promise<number>;
tidyUp: () => Promise<TidyUpResult | null>;
reorder: (order: number[]) => Promise<TidyUpResult | null>;
onChanged: (callback: () => void) => () => void;
/** Compatibility controls: cloud mode resolves no-op; `dev:rest` uses local watcher endpoints. */
watchActive: (draftNumber: number) => Promise<boolean>;
/** Cloud changes arrive through the workspace drain rather than this watcher. */
unwatchActive: () => Promise<boolean>;
onExternalChange: (callback: (draftNumber: number) => void) => () => void;
};
frontmatter: {
onSchemaChanged: (callback: () => void) => () => void;
};
workspace: {
getDir: () => Promise<string>;
setDir: () => Promise<string | null>;
listAll: () => Promise<WorkspaceEntry[]>;
register: (name: string, path: string) => Promise<WorkspaceEntry[]>;
remove: (path: string) => Promise<WorkspaceEntry[]>;
updateMetadata: (path: string, patch: WorkspaceMetadataPatch) => Promise<WorkspaceEntry[]>;
switchTo: (path: string) => Promise<string>;
listFiles: (path: string) => Promise<WorkspaceDirEntry[]>;
scaffold: (options: WorkspaceScaffoldOptions) => Promise<WorkspaceEntry[]>;
addExisting: (path: string) => Promise<WorkspaceEntry[]>;
};
dialog: {
openDirectory: () => Promise<string | null>;
openFile: () => Promise<string | null>;
createDirectory: (path: string) => Promise<void>;
openFiles: () => Promise<string[] | null>;
};
settings: {
get: () => Promise<AppSettings | null>;
save: (settings: AppSettings) => Promise<boolean>;
// Quit-path blocking write (#2782): Tauri routes it to a blocking Rust
// command; mock/REST fall back to a best-effort save.
saveBlocking: (settings: AppSettings) => Promise<boolean>;
};
fonts: {
list: () => Promise<Array<{ family: string; isMonospace: boolean }>>;
};
device: {
getName: () => Promise<string | null>;
setName: (name: string) => Promise<void>;
clearName: () => Promise<void>;
};
auth: {
getState: () => Promise<AuthState>;
login: () => Promise<void>;
logout: () => Promise<void>;
onStateChanged: (callback: (state: AuthState) => void) => () => void;
};
subscription: {
getInfo: () => Promise<SubscriptionInfo>;
startTrial: () => Promise<SubscriptionInfo>;
checkEntitlement: (feature: string) => Promise<boolean>;
getPortalUrl: () => Promise<string>;
onInfoChanged: (callback: (info: SubscriptionInfo) => void) => () => void;
};
cloudSync: {
getStatus: () => Promise<CloudSyncStatusInfo>;
triggerSync: () => Promise<CloudSyncStatusInfo>;
connect: () => Promise<void>;
disconnect: () => Promise<void>;
setupEncryption: (password: string) => Promise<boolean>;
verifyPassword: (password: string) => Promise<boolean>;
armEncryption: (password: string) => Promise<ArmEncryptionResult>;
getConflicts: () => Promise<CloudConflictItem[]>;
resolveConflicts: (resolutions: Map<string, "use-local" | "use-server" | "save-both">) => Promise<void>;
onStatusChanged: (callback: (status: CloudSyncStatusInfo) => void) => () => void;
onRemoteChange: (callback: (changes: CloudConflictItem[]) => void) => () => void;
};
assets: {
list: () => Promise<AssetEntry[]>;
saveFile: (filename: string, data: string) => Promise<string>;
importFile: (sourcePath: string) => Promise<string>;
readFile: (filename: string) => Promise<string | null>;
deleteFile: (filename: string) => Promise<boolean>;
};
publish: {
getProfile: () => Promise<PublicProfile | null>;
updateProfile: (data: { username: string; displayName: string; bio: string }) => Promise<PublicProfile>;
deleteProfile: () => Promise<void>;
publishPage: (data: { title: string; slug: string; contentMarkdown: string; description?: string }) => Promise<PublishedPage>;
updatePage: (id: string, data: { title?: string; slug?: string; contentMarkdown?: string; description?: string }) => Promise<PublishedPage>;
unpublishPage: (id: string) => Promise<void>;
listPages: () => Promise<{ pages: PublishedPage[]; total: number }>;
getPage: (id: string) => Promise<PublishedPage>;
onPagesChanged: (callback: () => void) => () => void;
};
generator: {
checkPath: (workspacePath: string) => Promise<PathClassification>;
scaffold: (appName: string, workspacePath: string, preset?: string, settings?: Record<string, unknown>, force?: boolean) => Promise<GeneratorRunResult>;
writeConfigs: (appName: string, workspacePath: string, force?: boolean) => Promise<GeneratorRunResult>;
assembleChild: (options: GeneratorAssembleChildOptions) => Promise<GeneratorAssembleResult>;
onAssembleProgress: (callback: (event: GeneratorAssembleProgressEvent) => void) => () => void;
findLeafForWorkspace: (workspacePath: string) => Promise<LeafAppInfo | null>;
openApp: (appPath: string) => Promise<void>;
};
window: {
setOpacity: (opacity: number) => Promise<void>;
print: () => Promise<void>;
setTitle: (title: string) => Promise<void>;
};
similarDocs: {
query: (draftContent: string, currentDraftNum: number | null, includePaths?: string[]) => Promise<SimilarDocResult[]>;
getContent: (path: string) => Promise<string | null>;
writeContent: (path: string, content: string) => Promise<boolean>;
rebuildIndex: () => Promise<void>;
};
chat: (messages: ChatMessage[], options: ChatOptions) => AsyncIterable<ChatEvent>;
inlineCommand: {
stream: (input: InlineCommandStreamInput) => AsyncIterable<LlmChatEvent>;
};
fileSearch: FileSearchAPI;
apiTokens: {
list: () => Promise<ApiTokenMeta[]>;
/** `input.scopes` omitted → `["full"]`; `input.workspaceId` omitted → unrestricted across the owner's workspaces. */
create: (input: ApiTokenCreateInput) => Promise<ApiTokenMintResult>;
revoke: (id: string) => Promise<void>;
};
getProjectRoot: () => Promise<string>;
getHomeDir: () => Promise<string>;
revealDirectory: (path: string) => Promise<void>;
}Key Types
interface MessageMeta {
filename: string;
title: string;
date: string | null;
createdAt: string | null;
updatedAt: string | null;
sidebarPosition: number;
modifiedAt: string;
snippet?: string;
}
interface MessageWithBody extends MessageMeta {
body: string;
}
interface PinEntry {
name: string;
type: "file" | "directory";
path: string;
title: string;
description: string;
modifiedAt: string;
children?: PinEntry[];
}
interface InboxDeleteResult {
newCount: number;
newActive: number;
}
interface TidyUpResult {
newCount: number;
newActive: number;
}
interface WorkspaceEntry {
path: string;
name: string;
}
interface AssetEntry {
filename: string;
sizeBytes: number;
modifiedAt: string;
}
type CloudSyncStatusType = "idle" | "syncing" | "synced" | "error" | "conflict" | "offline";
interface CloudSyncStatusInfo {
status: CloudSyncStatusType;
lastSyncedAt: string | null;
filesCount: number;
cursor: number;
connectedDevices: number;
error?: string;
}
interface CloudConflictItem {
path: string;
encryptedPath: string;
localVersion: number;
serverVersion: number;
type: "concurrent-edit" | "edit-delete" | "delete-edit";
}
interface AuthUser {
id: string;
email: string;
name: string;
picture?: string;
}
interface AuthState {
isAuthenticated: boolean;
user: AuthUser | null;
}Auth API
The auth domain manages user authentication state.
auth.getState()— Returns the currentAuthState(authenticated flag and user info)auth.login()— Starts the Better Auth browser handoffauth.logout()— Clears tokens and reverts to unauthenticated stateauth.onStateChanged(callback)— Registers a listener for auth state changes; returns an unsubscribe function
const backend = getBackend();
// Check auth state
const auth = await backend.auth.getState();
if (auth.isAuthenticated) {
console.log("Logged in as:", auth.user?.name);
}
// Login / logout
await backend.auth.login();
await backend.auth.logout();
// Listen for auth changes
const unsub = backend.auth.onStateChanged((state) => {
console.log("Authenticated:", state.isAuthenticated);
});Cloud Sync API
The cloudSync domain manages end-to-end encrypted cloud synchronization with real-time WebSocket updates.
cloudSync.getStatus()— Returns the currentCloudSyncStatusInfocloudSync.triggerSync()— Initiates a manual push + pull synccloudSync.connect()— Connects the WebSocket for real-time change streamingcloudSync.disconnect()— Disconnects the WebSocketcloudSync.setupEncryption(password)— Sets up the encryption password for the workspacecloudSync.verifyPassword(password)— Verifies an encryption passwordcloudSync.armEncryption(password)— Arms E2E encryption; branches on workspace state (creates or verifies key)cloudSync.getConflicts()— Returns pendingCloudConflictItem[]cloudSync.resolveConflicts(resolutions)— Resolves conflicts with aMap<path, strategy>cloudSync.onStatusChanged(callback)— Listens for cloud sync status changescloudSync.onRemoteChange(callback)— Listens for real-time remote change notifications
const backend = getBackend();
// Check cloud sync state
const status = await backend.cloudSync.getStatus();
console.log(status.status); // "idle" | "syncing" | "synced" | "error" | "conflict" | "offline"
// Connect for real-time updates
await backend.cloudSync.connect();
// Listen for remote changes
const unsub = backend.cloudSync.onRemoteChange((changes) => {
console.log("Remote changes:", changes.length);
});
// Trigger a manual sync
const updated = await backend.cloudSync.triggerSync();
// Handle conflicts
const conflicts = await backend.cloudSync.getConflicts();
const resolutions = new Map(conflicts.map((c) => [c.path, "use-server" as const]));
await backend.cloudSync.resolveConflicts(resolutions);Assets API
The assets namespace manages media and binary files (images, etc.) stored in the workspace:
const backend = getBackend();
// List all assets
const assets = await backend.assets.list();
// [{ filename: "photo.png", sizeBytes: 12345, modifiedAt: "2026-03-24T10:00:00Z" }]
// Save a base64-encoded file
await backend.assets.saveFile("photo.png", base64Data);
// Import a file from the local filesystem
await backend.assets.importFile("/path/to/image.png");
// Read file content (returns base64)
const data = await backend.assets.readFile("photo.png");
// Delete
await backend.assets.deleteFile("photo.png");Fonts API
The fonts namespace discovers installed system fonts:
const fonts = await backend.fonts.list();
// [{ family: "JetBrains Mono", isMonospace: true }, { family: "Arial", isMonospace: false }, ...]Window API
await backend.window.setOpacity(0.8); // Set window transparency (0.3–1.0)
await backend.window.print(); // Print the current preview pane
await backend.window.setTitle("My App"); // Override the OS window titleFilesystem API
await backend.revealDirectory("/path/to/workspace");
// Opens the directory in the OS file manager (Finder on macOS)TauriAdapter
createTauriAdapter() creates a BackendAPI that combines Tauri invoke() / listen() calls for surviving native operations with in-process access to the encrypted cloud workspace model for document domains. It is exported from the / subpath (not the main package entry).
import { createTauriAdapter } from "@takazudo/backend-bridge/tauri-adapter";
const adapter = createTauriAdapter();Event listeners use a syncListen helper that wraps Tauri's async listen() to return a synchronous unsubscribe function, matching the callback pattern used throughout the UI.
MockAdapter
createMockAdapter() creates an in-memory BackendAPI with a controls object for triggering events in tests.
import { createMockAdapter } from "@takazudo/backend-bridge";
const { api, controls } = createMockAdapter();
// Use in tests
initBackend(api);
// Trigger events
controls.triggerMessagesChanged("test.md");
controls.triggerNotesChanged("inbox");
// Access internal stores
controls.files.set("note.md", "# Hello");
controls.pinFiles.set("0:readme.md", "content");MockControls
interface MockControls {
triggerMessagesChanged: (filename?: string) => void;
triggerPinFileChanged: (entryPath: string) => void;
triggerDraftExternalChange: (draftNumber: number) => void;
triggerIncomingWorkspaceConflict: (conflict: IncomingWorkspaceConflict) => void;
triggerNotesChanged: (dir: string) => void;
triggerSchemaChanged: () => void;
setAuthState: (state: AuthState) => void;
setSubscriptionInfo: (info: SubscriptionInfo) => void;
triggerSubscriptionInfoChanged: (info: SubscriptionInfo) => void;
addRemoteFile: (path: string, content: string) => void;
files: Map<string, string>;
pinFiles: Map<string, string>;
remoteFiles: Map<string, string>;
setPinTree: (pinIndex: number, entries: PinEntry[] | null) => void;
setPinChildren: (pinIndex: number, dirPath: string, entries: PinEntry[] | null) => void;
simulateAuthFailure: (errorMessage?: string) => void;
simulateEncryptionKeyMismatch: (errorMessage?: string) => void;
simulateNetworkDownFirstSync: (errorMessage?: string) => void;
simulateCorruptedLocalSandbox: (errorMessage?: string) => void;
iosDegradedState: { type: string; errorMessage?: string } | null;
seedScaffoldTarget: (path: string, state: { hasSettings?: boolean; visibleChildren?: number }) => void;
setFindLeafResult: (result: LeafAppInfo | null) => void;
}RestAdapter
createRestAdapter() creates a BackendAPI that communicates with the Rust backend over HTTP/REST endpoints on localhost:3001 with Server-Sent Events (SSE) for real-time updates.
import { createRestAdapter } from "@takazudo/backend-bridge";
const adapter = createRestAdapter(); // defaults to http://localhost:3001
// or
const adapter = createRestAdapter("http://localhost:4000");The REST adapter maps each BackendAPI method to an HTTP endpoint (e.g., GET /api/messages, POST /). Real-time events use a shared EventSource connection with reference counting.
Default Exports
The package exports default values for cloud sync and auth state:
import { defaultCloudSyncStatus, defaultAuthState } from "@takazudo/backend-bridge";
// defaultCloudSyncStatus: { status: "idle", lastSyncedAt: null, filesCount: 0, cursor: 0, connectedDevices: 0 }
// defaultAuthState: { isAuthenticated: false, user: null }Dependencies
@takazudo/app-defaults— settings types@takazudo/cloud-crypto— encryption key derivation for cloud sync@takazudo/cloud-sync— cloud sync client (WebSocket manager, change tracker)@takazudo/file-utils— frontmatter parsing (used by MockAdapter)@takazudo/inline-command-skills— skill loader types (used by files namespace)
Tauri packages (@tauri-apps/api, @tauri-apps/plugin-dialog, @tauri-apps/plugin-shell) are peer dependencies — optional, required only when using the TauriAdapter in a real Tauri app.