useBoardActions
Relevant source files
The following files were used as context for generating this wiki page:
The useBoardActions composable provides the mutation layer for board and card operations in KanStack. It exposes functions for creating, moving, archiving, and renaming boards, columns, and cards. Each operation follows a consistent pattern: serialize the change to markdown, invoke a backend command to persist it, and return an updated workspace snapshot.
For workspace state management and queries, see useWorkspace. For card editing sessions, see useCardEditor.
Overview and Architecture
The useBoardActions composable sits between the UI layer and the backend, coordinating three key responsibilities:
- Serialization: Transforms structured board/card data into markdown using utilities from
serializeBoard.tsandserializeCard.ts - Persistence: Invokes Tauri backend commands to write markdown files to disk
- State Updates: Returns
WorkspaceSnapshotobjects thatuseWorkspaceapplies to reactive state
Sources: src/composables/useBoardActions.ts:1-449
Composable Interface
The useBoardActions function accepts an options object that provides access to the current workspace state without creating circular dependencies with useWorkspace:
interface UseBoardActionsOptions {
getBoardsBySlug: () => Record<string, KanbanBoardDocument>
getWorkspaceRoot: () => string | null
getBoardFilesBySlug: () => Record<string, WorkspaceFileSnapshot>
getCardsBySlug: () => Record<string, KanbanCardDocument>
}The composable returns an object containing:
- Action functions: Async functions that perform mutations
- Loading state refs: Reactive boolean flags indicating operation progress
Sources: src/composables/useBoardActions.ts:43-48, src/composables/useBoardActions.ts:413-433
Operation Pattern
All mutation operations in useBoardActions follow a consistent flow:
Key characteristics of this pattern:
- Loading State Management: Each operation sets a loading flag before starting and clears it in a
finallyblock - Early Returns: Operations check preconditions (workspace root exists, not already in progress) and return
nullif invalid - Error Handling: All backend invocations are wrapped in try/catch blocks that log errors and return
null - Snapshot Returns: Successful operations return a
WorkspaceSnapshotthat the caller can apply to reactive state
Sources: src/composables/useBoardActions.ts:60-81, src/composables/useBoardActions.ts:107-149
Card Operations
Creating Cards
The createCard function creates a new card in the first non-archive column of a board:
| Step | Action | Implementation |
|---|---|---|
| 1 | Generate unique slug | Uses getNextCardSlug() to find next available untitled-card-N slug |
| 2 | Serialize card markdown | Calls serializeCardMarkdown() with title and empty body |
| 3 | Update board markdown | Calls addBoardCardMarkdown() to insert wikilink in target column |
| 4 | Invoke backend | Calls create_card_in_board command to atomically write both files |
| 5 | Return result | Returns object with card slug and workspace snapshot |
The card is placed in the first section of the first non-archive column. If all columns are archive columns, it defaults to the first column.
Sources: src/composables/useBoardActions.ts:107-149, src/composables/useBoardActions.ts:436-444
Moving Cards
The moveCard function relocates a card to a different column, section, or position within the same board:
async function moveCard(board: KanbanBoardDocument, input: MoveBoardCardInput)The MoveBoardCardInput interface specifies:
cardSlug: Full slug of the card being movedtargetColumnNameandtargetColumnSlug: Destination columntargetSectionNameandtargetSectionSlug: Destination section (ornullfor default section)targetIndex: Position within the section's card list
The operation uses moveBoardCardMarkdown() to regenerate the board's markdown with the card in its new position, then persists via save_board_file.
Sources: src/composables/useBoardActions.ts:60-81, src/utils/serializeBoard.ts:1-14
Archiving Cards
The archiveCard and archiveCards functions move cards to the archive column:
archiveCard(board, cardSlug): Archives a single cardarchiveCards(board, cardSlugs): Archives multiple cards in batch
Both functions:
- Generate updated board markdown using
archiveBoardCardMarkdown()orarchiveBoardCardsMarkdown() - Automatically create an "Archive" column if one doesn't exist
- Place archived cards at the end of the archive column
The archive column is special-cased throughout the codebase via isArchiveColumnSlug() checks.
Sources: src/composables/useBoardActions.ts:348-375, src/utils/serializeBoard.ts:7-8
Column Operations
Column operations affect all boards in the workspace simultaneously, maintaining consistency across the board hierarchy.
Column Synchronization Model
The deriveWorkspaceColumns() utility extracts a canonical column set from the "preferred board" (typically the current board). All column operations apply the same transformation to every board.
Sources: src/composables/useBoardActions.ts:191-220, src/utils/workspaceColumns.ts
Adding Columns
The addColumn function creates a new column across all boards:
- Generate column name and slug (
"Untitled Column"and unique slug) - Derive current global columns from the preferred board
- Insert new column before the archive column using
insertWorkspaceColumnBeforeArchive() - Apply to all boards via
syncBoardColumnsMarkdown() - Persist all boards atomically via
save_workspace_boards
Archive columns are always kept last, even when new columns are added.
Sources: src/composables/useBoardActions.ts:191-221
Renaming Columns
The renameColumn function updates a column's name and slug across all boards:
async function renameColumn(
preferredBoard: KanbanBoardDocument,
currentSlug: string,
nextName: string
)Special cases:
- Archive columns cannot be renamed (throws error)
- The new slug is derived from
slugifySegment(nextName) - If the derived slug conflicts with existing columns,
getNextAvailableSlug()appends a numeric suffix - All boards receive the same rename via
renameBoardColumnMarkdown()
Sources: src/composables/useBoardActions.ts:223-254
Deleting Columns
The deleteColumn function removes a column from all boards:
async function deleteColumn(columnSlug: string)Deletion is blocked if any board has cards in the target column:
const hasCards = boards.some(
(board) => countCardsInColumn(board.columns.find((column) => column.slug === columnSlug)) > 0
)
if (hasCards) {
return { blocked: true, snapshot: null }
}If deletion proceeds, deleteBoardColumnMarkdown() removes the column heading and any empty sections from all boards.
Sources: src/composables/useBoardActions.ts:256-285
Reordering Columns
The reorderColumns function changes column order across all boards:
async function reorderColumns(
preferredBoard: KanbanBoardDocument,
draggedSlug: string,
targetIndex: number
)The operation:
- Derives global columns from the preferred board
- Reorders using
reorderWorkspaceColumns()utility - Applies new order to all boards via
reorderBoardColumnsMarkdown() - Persists via
save_workspace_boards
The archive column always remains last regardless of the specified target index.
Sources: src/composables/useBoardActions.ts:287-304
Column Batch Persistence
The saveColumns helper function centralizes the logic for persisting column changes across all boards:
async function saveColumns(
boards: Array<{ path: string; content: string }>,
mode: 'create' | 'rename' | 'delete'
)It manages the appropriate loading state flag based on the mode and invokes the save_workspace_boards backend command, which atomically writes all board files.
Sources: src/composables/useBoardActions.ts:306-346
Board Operations
Creating Boards
The createBoard function opens a directory picker and creates a new board at the selected location:
New boards inherit their column structure from existing boards in the workspace. If no boards exist, they use a default set of four columns defined in DEFAULT_NEW_BOARD_COLUMNS.
Sources: src/composables/useBoardActions.ts:151-189, src/composables/useBoardActions.ts:36-41
Renaming Boards
The renameBoard function updates a board's title in its frontmatter:
async function renameBoard(board: KanbanBoardDocument, title: string)The operation:
- Normalizes the title (trims whitespace)
- Returns early if the title is unchanged
- Uses
renameBoardMarkdown()to update the frontmattertitlefield - Persists via
save_board_file - Returns the board slug and snapshot (or null if title unchanged)
Only the frontmatter is modified; the board's column structure remains intact.
Sources: src/composables/useBoardActions.ts:377-411
Saving Board Settings
The saveBoardSettings function updates a board's %% kanban:settings %% block:
async function saveBoardSettings(
board: KanbanBoardDocument,
settings: KanbanBoardSettings
)Settings include preferences like:
show-archive-column: Whether to display the archive columnshow-sub-boards: Whether to display the sub-boards section
The operation uses updateBoardSettingsMarkdown() to update the JSON settings block while preserving the rest of the markdown content.
Sources: src/composables/useBoardActions.ts:83-105
Loading State Management
The composable maintains eight loading state refs to prevent concurrent operations and provide UI feedback:
| Ref | Operations | Type |
|---|---|---|
isCreatingCard | createCard() | Card creation |
isCreatingColumn | addColumn() | Column creation |
isCreatingBoard | createBoard() | Board creation |
isDeletingColumn | deleteColumn() | Column deletion |
isMovingCard | moveCard(), archiveCard(), archiveCards() | Card movement |
isRenamingColumn | renameColumn(), reorderColumns() | Column renaming/reordering |
isRenamingBoard | renameBoard() | Board renaming |
isSavingPreference | saveBoardSettings() | Settings updates |
Each loading state follows the pattern:
if (isLoadingState.value) {
return null // Prevent concurrent operations
}
isLoadingState.value = true
try {
// Perform operation
} finally {
isLoadingState.value = false // Always clear flag
}UI components can bind to these refs to show loading spinners or disable controls during operations.
Sources: src/composables/useBoardActions.ts:51-58, src/composables/useBoardActions.ts:60-81
Backend Command Integration
The composable invokes four Tauri backend commands:
Each command returns a WorkspaceSnapshot containing the updated file contents, which the composable returns to the caller for application to reactive state.
Sources: src/composables/useBoardActions.ts:70-74, src/composables/useBoardActions.ts:135-141, src/composables/useBoardActions.ts:178-181, src/composables/useBoardActions.ts:328-331
Serialization Function Mapping
The composable delegates markdown generation to specialized utilities in serializeBoard.ts:
| Operation | Serialization Function | Purpose |
|---|---|---|
| Move card | moveBoardCardMarkdown() | Relocates wikilink to new column/section/index |
| Archive card(s) | archiveBoardCardMarkdown(), archiveBoardCardsMarkdown() | Moves wikilink(s) to archive column |
| Add card | addBoardCardMarkdown() | Inserts new wikilink at specified position |
| Create board | createBoardMarkdownFromColumns() | Generates complete board file with frontmatter and columns |
| Rename board | renameBoardMarkdown() | Updates frontmatter title |
| Add column | syncBoardColumnsMarkdown() | Inserts new ## Column heading |
| Rename column | renameBoardColumnMarkdown() | Updates ## Column heading and slug |
| Delete column | deleteBoardColumnMarkdown() | Removes ## Column heading |
| Reorder columns | reorderBoardColumnsMarkdown() | Rearranges ## Column headings |
| Update settings | updateBoardSettingsMarkdown() | Modifies %% kanban:settings %% JSON block |
These functions preserve board structure like sections, settings blocks, and card ordering while applying the requested mutation.
Sources: src/composables/useBoardActions.ts:12-26, src/utils/serializeBoard.ts:1-14, src/utils/serializeBoardSettings.ts
Error Handling
All operations follow a consistent error handling pattern:
- Precondition Checks: Early return
nullif workspace root is missing or operation already in progress - Try/Catch Blocks: Wrap backend invocations to catch and log errors
- Error Logging: Use
console.error()with descriptive messages - Null Returns: Return
nullon failure, allowing callers to detect errors - Finally Blocks: Always clear loading states even if errors occur
Example from moveCard:
try {
return await invoke<WorkspaceSnapshot>('save_board_file', {
root: workspaceRoot,
path: board.path,
content: nextContent,
})
} catch (error) {
console.error('Failed to move card', error)
return null
} finally {
isMovingCard.value = false
}Callers should check for null returns and handle errors appropriately in the UI layer.
Sources: src/composables/useBoardActions.ts:69-80, src/composables/useBoardActions.ts:134-148
