All files / src/domain kanban.ts

88.37% Statements 76/86
92.85% Branches 26/28
66.66% Functions 12/18
88.37% Lines 76/86

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87  1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 1x 1x 1x     1x 1x 31x 31x 1x 1x 42x 1x 1x 42x 1x 1x 42x 31x 31x 42x 1x 1x 42x 42x 1x 1x 2x 14x 14x 14x 14x 2x 2x 1x 1x 1x                
import type { UserStory } from "./types.js"
import { sortStoriesById } from "./sort-by-id.js"
 
/** Board columns โ€” derived from SQLite `ready` + `status`, not stored separately. */
export type KanbanColumnId =
  | "backlog"
  | "todo"
  | "๐Ÿ”ถ"
  | "๐Ÿงช"
  | "โœ…"
  | "๐ŸงŠ"
  | "๐Ÿšซ"
 
export type KanbanColumnGroup = {
  columnId: KanbanColumnId
  stories: UserStory[]
}
 
export const KANBAN_COLUMN_ORDER: KanbanColumnId[] = [
  "backlog",
  "todo",
  "๐Ÿ”ถ",
  "๐Ÿงช",
  "โœ…",
  "๐ŸงŠ",
  "๐Ÿšซ",
]
 
/** Display titles for board column headers (UI only โ€” not US `status`). */
const COLUMN_HEADER_LABELS: Record<KanbanColumnId, string> = {
  backlog: "๐Ÿ“‹ Backlog",
  todo: "๐Ÿ“Œ Todo",
  "๐Ÿ”ถ": "๐Ÿ”ถ Partial",
  "๐Ÿงช": "๐Ÿงช Tests",
  "โœ…": "โœ… Done",
  "๐ŸงŠ": "๐ŸงŠ Frozen",
  "๐Ÿšซ": "๐Ÿšซ Deprecated",
}
 
export function columnHeaderLabel(columnId: KanbanColumnId): string {
  return COLUMN_HEADER_LABELS[columnId]
}
 
/** @deprecated use columnHeaderLabel */
export function columnLabel(columnId: KanbanColumnId): string {
  return columnHeaderLabel(columnId)
}
 
export function isReadyForTodo(story: UserStory): boolean {
  return story.ready === true
}
 
export function resolveKanbanColumn(story: UserStory): KanbanColumnId {
  if (story.status === "๐ŸงŠ") {
    return "๐ŸงŠ"
  }
  if (story.status === "๐Ÿšซ") {
    return "๐Ÿšซ"
  }
  if (story.status === "โŒ") {
    return isReadyForTodo(story) ? "todo" : "backlog"
  }
  if (story.tests === "required" && story.testsStatus === "pending") {
    return "๐Ÿงช"
  }
  return story.status
}
 
export function groupStoriesForKanban(stories: UserStory[]): KanbanColumnGroup[] {
  return KANBAN_COLUMN_ORDER.map((columnId) => ({
    columnId,
    stories: sortStoriesById(
      stories.filter((story) => resolveKanbanColumn(story) === columnId),
    ),
  }))
}
 
/** Columns visible by default (hide empty Frozen / Deprecated). */
export function visibleBoardColumns(groups: KanbanColumnGroup[]): KanbanColumnGroup[] {
  return groups.filter(({ columnId, stories }) => {
    if (columnId === "๐ŸงŠ" || columnId === "๐Ÿšซ") {
      return stories.length > 0
    }
    return true
  })
}