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
Overview
Section titled “Overview”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.
Architecture
Section titled “Architecture”File Structure
Section titled “File Structure”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 handlersKey Design Decisions
Section titled “Key Design Decisions”1. SQLite Database Per Workspace
Section titled “1. SQLite Database Per Workspace”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
2. WAL Mode Connection Pool
Section titled “2. WAL Mode Connection Pool”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 = WALforeign_keys = ONsynchronous = NORMALcache_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.
3. Consolidated V001 Baseline Migration
Section titled “3. Consolidated V001 Baseline Migration”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.
4. Environment-Aware Paths
Section titled “4. Environment-Aware Paths”AppPaths resolves storage locations based on build type:
| Environment | Detection | Workspaces Directory | Settings Location |
|---|---|---|---|
| Dev | cfg!(debug_assertions) | {project}/.data/workspaces/ | {project}/.data/ |
| Production | Release build | ~/Inklings/Workspaces/ | Platform app data dir + /Inklings/ |
A default workspace named “Default” is auto-created at {workspaces_dir}/Default/ on first launch.
5. Cached-DB Repository Wiring
Section titled “5. Cached-DB Repository Wiring”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.
6. Settings Stored Separately
Section titled “6. Settings Stored Separately”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.
Domain Rules
Section titled “Domain Rules”| Rule | Enforcement |
|---|---|
| Flat workspaces | No nesting allowed. Each workspace is independent. |
| Workspace scoping | All entities (pages, blocks, tags, attachments, etc.) belong to exactly one workspace. |
| Stable UUID | Each workspace has a Uuid that persists across renames. |
| Filesystem-safe names | WorkspaceName validates: non-empty, max 100 chars, no forbidden chars (/ \ : * ? " < > | NUL), no reserved Windows names, no leading/trailing dots or whitespace. |
| Icon validation | try_set_icon() validates icon name and color before mutation. Invalid values are rejected without modifying the entity. |
Key Code Paths
Section titled “Key Code Paths”Initialize Workspace
Section titled “Initialize Workspace”initialize_workspaceTauri command receives optional path and name- Falls back to
AppPaths::default_workspace_path()if no path provided InitializeWorkspaceUseCase::execute()checks if workspace already exists at path- If exists: loads and returns existing workspace
- If new: creates
Workspaceentity, callsWorkspaceRepository::create()
SqliteWorkspaceRepository::create(): creates directory, opens DB (runs migrations), inserts workspace row- Idempotency guard: skips expensive re-initialization if workspace path matches current
wire_workspace_db(): opensWorkspaceDatabasepool, replaces allSqlite*Repositoryinstanceswire_workspace_agents_db(): opens workspace-levelagents.db- Best-effort startup: seed builtin layouts, start embedding pipeline, start MCP server, wire agent manager
WriteEffectCoordinator::on_workspace_created()registers side-effect listeners
Open Workspace
Section titled “Open Workspace”open_workspaceTauri command validates path and checksinklings.dbexistsSqliteWorkspaceRepository::load(): opens DB, reads workspace row fromworkspacetable- Same idempotency guard and wiring sequence as Initialize
Close Workspace
Section titled “Close Workspace”- Stops agent harness manager (interrupt + drop)
- Stops MCP server (Drop triggers graceful shutdown)
- Stops task runner with 3-second timeout (
shutdown_and_wait) - Clears embedding task handle
- Clears
current_workspacestate toNone
Rename Workspace
Section titled “Rename Workspace”RenameWorkspaceUseCase::execute()requiresCapability::WorkspaceManage- Validates new name via
WorkspaceName::new()(domain validation) - Calculates new path (same parent directory, new folder name)
SqliteWorkspaceRepository::rename():- Renames the directory on disk
- Opens DB at new location, updates
workspace.nameandupdated_at - On DB failure: rolls back filesystem rename
- Updates
current_workspaceinAppState - Best-effort: updates recent workspaces list (remove old path, add new path), refreshes native menu
Set Workspace Icon
Section titled “Set Workspace Icon”SetWorkspaceIconUseCase::execute()requiresCapability::WorkspaceManage- Loads workspace, calls
workspace.try_set_icon()for validation - Saves via
WorkspaceRepository::save()(updates icon columns in DB) - Reloads and updates
current_workspaceinAppState - Best-effort: updates icon in recent workspaces list
Error Handling
Section titled “Error Handling”| Error | Context | Handling |
|---|---|---|
NotFound | open_workspace with invalid path | Returns CommandError::NotFound |
AlreadyExists | create when inklings.db already present | Returns error (use load instead) |
InvalidName | rename with forbidden chars / reserved name | Returns validation error to frontend |
RenameFailed | Filesystem rename failure | Returns error; if DB update fails after rename, attempts rollback |
IncompatibleVersion | DB version newer than app version | Returns migration error (user needs to update app) |
ConnectionError | DB open failure | wire_workspace_db logs and skips (best-effort, never blocks workspace open) |
NoWorkspace | Command called before workspace is opened | Returns CommandError::NoWorkspace |
Tauri Commands
Section titled “Tauri Commands”| Command | Description |
|---|---|
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 |
Related
Section titled “Related”- 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?
Thanks for your feedback!