Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/views/kanban-view.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Access these options through the Bases view settings panel:
- **Hide Empty Columns**: When enabled, columns containing no tasks are hidden from the view
- **Show items in multiple columns**: When enabled (default), tasks with multiple values in list properties (contexts, tags, projects) appear in each individual column. For example, a task with `contexts: [work, call]` appears in both the "work" and "call" columns. When disabled, tasks appear in a single combined column (e.g., "work, call")
- **Column Order**: Managed automatically when dragging column headers. Stores custom column ordering
- **Pinned Columns**: Comma-separated values that always render as columns, even when empty. Exempt from "Hide Empty Columns" so they remain available as drop targets
A common setup is to keep one board grouped by status and another grouped by project or context, each in a separate `.base` file.

## Interface Layout
Expand Down Expand Up @@ -120,14 +121,15 @@ views:
swimLane: task.priority
columnWidth: 300
hideEmptyColumns: true
pinnedColumns: to-do, in-progress, done
---
```

This configuration creates a Kanban board with:
- Columns based on task status
- Swimlanes based on task priority
- 300px column width
- Empty columns hidden
- Empty columns hidden, except for the pinned `to-do`, `in-progress`, and `done` columns which always render

## Filtering and Sorting

Expand Down
56 changes: 52 additions & 4 deletions src/bases/KanbanView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class KanbanView extends BasesViewBase {
private explodeListColumns = true; // Show items with list properties in multiple columns
private consolidateStatusIcon = false; // Show status icon in header only when grouped by status
private columnOrders: Record<string, string[]> = {};
private pinnedColumns: string[] = [];
private configLoaded = false; // Track if we've successfully loaded config
/**
* Threshold for enabling virtual scrolling in kanban columns/swimlane cells.
Expand Down Expand Up @@ -197,6 +198,25 @@ export class KanbanView extends BasesViewBase {
const consolidateValue = this.config.get('consolidateStatusIcon');
this.consolidateStatusIcon = consolidateValue === true; // Default to false if not set

// Read pinned columns. Comma-separated string (settings panel) or
// YAML array (authored in `.base`). Normalize either shape.
// Parsed before the columnOrder JSON.parse below so a malformed
// columnOrder cannot leave stale pinnedColumns state.
const rawPinned = this.config.get("pinnedColumns");
const pinnedSource: unknown[] = Array.isArray(rawPinned)
? rawPinned
: typeof rawPinned === "string"
? rawPinned.split(",")
: [];
const seenPinned = new Set<string>();
this.pinnedColumns = [];
for (const value of pinnedSource) {
const str = String(value ?? "").trim();
if (str.length === 0 || seenPinned.has(str)) continue;
seenPinned.add(str);
this.pinnedColumns.push(str);
}

// Read column orders
const columnOrderStr = (this.config.get("columnOrder") as string) || "{}";
this.columnOrders = JSON.parse(columnOrderStr);
Expand Down Expand Up @@ -583,6 +603,9 @@ export class KanbanView extends BasesViewBase {
// Augment with empty priority columns if grouping by priority
this.augmentWithEmptyPriorityColumns(groups, groupByPropertyId);

// Augment with pinned columns regardless of groupBy property
this.augmentWithPinnedColumns(groups);

return groups;
}

Expand Down Expand Up @@ -726,6 +749,18 @@ export class KanbanView extends BasesViewBase {
}
}

/**
* Augment groups with empty buckets for pinned column keys.
* Runs regardless of groupBy.
*/
private augmentWithPinnedColumns(groups: Map<string, TaskInfo[]>): void {
for (const key of this.pinnedColumns) {
if (!groups.has(key)) {
groups.set(key, []);
}
}
}

private async renderFlat(
groups: Map<string, TaskInfo[]>,
allGroups: Map<string, TaskInfo[]>
Expand Down Expand Up @@ -760,8 +795,13 @@ export class KanbanView extends BasesViewBase {
for (const groupKey of orderedKeys) {
const tasks = groups.get(groupKey) || [];

// Filter empty columns if option enabled
if (this.hideEmptyColumns && tasks.length === 0) {
// Filter empty columns if option enabled. Pinned columns are exempt
// so they remain visible as drop targets.
if (
this.hideEmptyColumns &&
tasks.length === 0 &&
!this.pinnedColumns.includes(groupKey)
) {
continue;
}

Expand Down Expand Up @@ -3031,8 +3071,16 @@ export class KanbanView extends BasesViewBase {
const savedOrder = this.columnOrders[groupBy];

if (!savedOrder || savedOrder.length === 0) {
// No saved order - use natural order (alphabetical)
return actualKeys.sort();
// No saved order: pinned columns first (in pinnedColumns array order),
// remaining keys alphabetical.
if (this.pinnedColumns.length === 0) {
return actualKeys.sort();
}
const pinnedPresent = this.pinnedColumns.filter((k) => actualKeys.includes(k));
const remaining = actualKeys
.filter((k) => !this.pinnedColumns.includes(k))
.sort();
return [...pinnedPresent, ...remaining];
}

const ordered: string[] = [];
Expand Down
7 changes: 7 additions & 0 deletions src/bases/registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ export async function registerBasesTaskList(plugin: TaskNotesPlugin): Promise<vo
placeholder: "Auto-managed when dragging columns",
default: "{}",
},
{
type: "text",
key: "pinnedColumns",
displayName: "Pinned Columns",
placeholder: "Comma-separated column values",
default: "",
},
{
type: "dropdown",
key: "expandedRelationshipFilterMode",
Expand Down
Loading