Skip to content
Documentation GitHub
Content

Workspace System

Status: Shipped Crates: crates/domain/src/workspace.rs, crates/domain/src/workspace_name.rs, crates/application/src/workspace/ Infrastructure: crates/infrastructure/sqlite/src/workspace/workspace_repository.rs, crates/infrastructure/sqlite/src/connection.rs Framework: apps/desktop/src-tauri/src/commands/workspace.rs, apps/desktop/src-tauri/src/state.rs, apps/desktop/src-tauri/src/paths.rs

A workspace is the top-level container for all content in Inklings. Each workspace maps to a directory on disk containing a dedicated SQLite database (inklings.db) that stores pages, blocks, tags, attachments, event log entries, bookmarks, embeddings, and all other workspace-scoped entities. Workspaces are flat (no nesting) and each entity belongs to exactly one workspace.

Settings are stored separately in a JSON file outside the workspace directory, managed by JsonSettingsRepository.

crates/domain/src/
workspace.rs # Workspace entity (id, name, path, description, icon, icon_color)
workspace_name.rs # WorkspaceName value object (filesystem-safe validation)
crates/application/src/workspace/
mod.rs # Re-exports
services.rs # WorkspaceRepository trait + WorkspaceRepositoryError
initialize.rs # InitializeWorkspaceUseCase
rename.rs # RenameWorkspaceUseCase (requires WorkspaceManage capability)
set_icon.rs # SetWorkspaceIconUseCase (requires WorkspaceManage capability)
crates/infrastructure/sqlite/src/
connection.rs # WorkspaceDatabase (WAL pool), workspace_db_path()
migrations/mod.rs # WORKSPACE_V001 consolidated baseline, version checking
workspace/
workspace_repository.rs # SqliteWorkspaceRepository (create, load, exists, save, rename)
apps/desktop/src-tauri/src/
paths.rs # AppPaths (environment-aware path resolution)
state.rs # AppState, WorkspaceBundle, wire_workspace_db()
commands/workspace.rs # Tauri command handlers

Each workspace is a directory containing inklings.db. The database holds all workspace metadata and content. This replaced an earlier file-based design (workspace.json + markdown files). Benefits:

  • ACID transactions for all mutations
  • FTS5 full-text search built into the storage layer
  • Single file to backup/move (alongside the attachment directory)
  • Schema versioning via rusqlite_migration

WorkspaceDatabase uses a 1-writer + 4-reader connection pool with SQLite WAL mode. All connections are configured with:

  • busy_timeout = 5000 (set first, before any pragma that acquires a lock)
  • journal_mode = WAL
  • foreign_keys = ON
  • synchronous = NORMAL
  • cache_size = -2000 (2 MB)

Readers use round-robin selection via AtomicUsize. The writer connection is protected by parking_lot::Mutex. Transactions always use the writer connection.

The schema uses a single consolidated WORKSPACE_V001 migration that creates the entire workspace schema (pages, blocks, FTS5, sync infrastructure, event log, bookmarks, attachments, types, properties, layouts, tags, references). Subsequent migrations are added as WORKSPACE_V002, etc. Current schema version is tracked via CURRENT_WORKSPACE_VERSION constant. Migrations are one-way only (no downgrade support). Version compatibility is checked on open to detect databases from newer app versions.

AppPaths resolves storage locations based on build type:

EnvironmentDetectionWorkspaces DirectorySettings Location
Devcfg!(debug_assertions){project}/.data/workspaces/{project}/.data/
ProductionRelease build~/Inklings/Workspaces/Platform app data dir + /Inklings/

A default workspace named “Default” is auto-created at {workspaces_dir}/Default/ on first launch.

All workspace-scoped Sqlite*Repository instances are wrapped in parking_lot::Mutex in AppState. When a workspace is opened, wire_workspace_db() opens the WorkspaceDatabase connection pool and replaces every repository’s inner state with with_cached_db(Arc::clone(&db)). This avoids reopening the database on every operation and ensures all repos share the same connection pool.

Workspace settings (including recent workspaces list) are stored in a JSON file via JsonSettingsRepository, located in the storage_dir (not inside any workspace). This allows settings to persist across workspace switches and survive workspace deletion.

RuleEnforcement
Flat workspacesNo nesting allowed. Each workspace is independent.
Workspace scopingAll entities (pages, blocks, tags, attachments, etc.) belong to exactly one workspace.
Stable UUIDEach workspace has a Uuid that persists across renames.
Filesystem-safe namesWorkspaceName validates: non-empty, max 100 chars, no forbidden chars (/ \ : * ? " < > | NUL), no reserved Windows names, no leading/trailing dots or whitespace.
Icon validationtry_set_icon() validates icon name and color before mutation. Invalid values are rejected without modifying the entity.
  1. initialize_workspace Tauri command receives optional path and name
  2. Falls back to AppPaths::default_workspace_path() if no path provided
  3. InitializeWorkspaceUseCase::execute() checks if workspace already exists at path
    • If exists: loads and returns existing workspace
    • If new: creates Workspace entity, calls WorkspaceRepository::create()
  4. SqliteWorkspaceRepository::create(): creates directory, opens DB (runs migrations), inserts workspace row
  5. Idempotency guard: skips expensive re-initialization if workspace path matches current
  6. wire_workspace_db(): opens WorkspaceDatabase pool, replaces all Sqlite*Repository instances
  7. wire_workspace_agents_db(): opens workspace-level agents.db
  8. Best-effort startup: seed builtin layouts, start embedding pipeline, start MCP server, wire agent manager
  9. WriteEffectCoordinator::on_workspace_created() registers side-effect listeners
  1. open_workspace Tauri command validates path and checks inklings.db exists
  2. SqliteWorkspaceRepository::load(): opens DB, reads workspace row from workspace table
  3. Same idempotency guard and wiring sequence as Initialize
  1. Stops agent harness manager (interrupt + drop)
  2. Stops MCP server (Drop triggers graceful shutdown)
  3. Stops task runner with 3-second timeout (shutdown_and_wait)
  4. Clears embedding task handle
  5. Clears current_workspace state to None
  1. RenameWorkspaceUseCase::execute() requires Capability::WorkspaceManage
  2. Validates new name via WorkspaceName::new() (domain validation)
  3. Calculates new path (same parent directory, new folder name)
  4. SqliteWorkspaceRepository::rename():
    • Renames the directory on disk
    • Opens DB at new location, updates workspace.name and updated_at
    • On DB failure: rolls back filesystem rename
  5. Updates current_workspace in AppState
  6. Best-effort: updates recent workspaces list (remove old path, add new path), refreshes native menu
  1. SetWorkspaceIconUseCase::execute() requires Capability::WorkspaceManage
  2. Loads workspace, calls workspace.try_set_icon() for validation
  3. Saves via WorkspaceRepository::save() (updates icon columns in DB)
  4. Reloads and updates current_workspace in AppState
  5. Best-effort: updates icon in recent workspaces list
ErrorContextHandling
NotFoundopen_workspace with invalid pathReturns CommandError::NotFound
AlreadyExistscreate when inklings.db already presentReturns error (use load instead)
InvalidNamerename with forbidden chars / reserved nameReturns validation error to frontend
RenameFailedFilesystem rename failureReturns error; if DB update fails after rename, attempts rollback
IncompatibleVersionDB version newer than app versionReturns migration error (user needs to update app)
ConnectionErrorDB open failurewire_workspace_db logs and skips (best-effort, never blocks workspace open)
NoWorkspaceCommand called before workspace is openedReturns CommandError::NoWorkspace
CommandDescription
initialize_workspace(path?, name?)Create or open workspace at path (or default)
open_workspace(path)Open existing workspace
get_current_workspace()Get currently active workspace (or null)
close_workspace()Stop subsystems and clear current workspace
workspace_exists(path)Check if valid workspace exists at path
get_default_workspace_path()Get environment-aware default path
get_workspaces_dir()Get environment-aware workspaces directory
check_folder_status(path)Check folder status: Empty, NonEmpty, AlreadyWorkspace, NotADirectory, NotAccessible
rename_workspace(new_name)Rename current workspace (folder + metadata)
set_workspace_icon(icon?, icon_color?)Set or clear workspace icon
reload_embedding_pipeline()Hot-reload embedding model after download
  • Domain Rules — workspace scoping and flat workspace invariants
  • crates/infrastructure/json/ — JsonSettingsRepository (settings and recent workspaces)
  • crates/infrastructure/sqlite/src/migrations/mod.rs — schema versioning

Was this page helpful?