@industry-theme/backlogmd-kanban-panel 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"KanbanPanel.d.ts","sourceRoot":"","sources":["../../src/panels/KanbanPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAGrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AA4LpD;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAMrD,CAAC"}
1
+ {"version":3,"file":"KanbanPanel.d.ts","sourceRoot":"","sources":["../../src/panels/KanbanPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAGrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AA6LpD;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAMrD,CAAC"}
@@ -7,29 +7,39 @@ export interface ColumnState {
7
7
  hasMore: boolean;
8
8
  isLoadingMore: boolean;
9
9
  }
10
+ /** Source column names (directory-based) */
11
+ export type SourceColumn = 'tasks' | 'completed';
12
+ /** Display labels for source columns */
13
+ export declare const SOURCE_DISPLAY_LABELS: Record<SourceColumn, string>;
10
14
  export interface UseKanbanDataResult {
11
15
  tasks: Task[];
12
- statuses: string[];
16
+ /** Source columns: "tasks" and "completed" */
17
+ sources: SourceColumn[];
13
18
  isLoading: boolean;
14
19
  error: string | null;
15
20
  isBacklogProject: boolean;
16
- tasksByStatus: Map<string, Task[]>;
21
+ tasksBySource: Map<string, Task[]>;
17
22
  /** Per-column pagination state */
18
23
  columnStates: Map<string, ColumnState>;
19
- /** Load more tasks for a specific status column */
20
- loadMore: (status: string) => Promise<void>;
24
+ /** Load more tasks for a specific source column */
25
+ loadMore: (source: SourceColumn) => Promise<void>;
21
26
  refreshData: () => Promise<void>;
22
27
  updateTaskStatus: (taskId: string, newStatus: string) => Promise<void>;
23
28
  }
24
29
  interface UseKanbanDataOptions {
25
30
  context?: PanelContextValue;
26
31
  actions?: PanelActions;
27
- /** Number of tasks to load per column (default: 10) */
28
- pageSize?: number;
32
+ /** Number of active tasks to load (default: 20) */
33
+ tasksLimit?: number;
34
+ /** Number of completed tasks to load (default: 5) */
35
+ completedLimit?: number;
29
36
  }
30
37
  /**
31
- * Hook for managing kanban board data with pagination
32
- * Integrates with Backlog.md via @backlog-md/core
38
+ * Hook for managing kanban board data with lazy loading
39
+ *
40
+ * Uses 2-column view (Active/Completed) based on directory structure.
41
+ * Only loads task content for displayed items (no file reads on init).
42
+ * Completed tasks are sorted by ID descending (most recent first).
33
43
  */
34
44
  export declare function useKanbanData(options?: UseKanbanDataOptions): UseKanbanDataResult;
35
45
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,kCAAkC;IAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CAmUrB"}
1
+ {"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,WAAW,CAAC;AAEjD,wCAAwC;AACxC,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAG9D,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,8CAA8C;IAC9C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,kCAAkC;IAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CAoUrB"}
@@ -526,10 +526,32 @@ function extractIdFromPath(filePath) {
526
526
  }
527
527
  return filename.replace(/\.md$/, "");
528
528
  }
529
+ function extractTitleFromPath(filePath) {
530
+ const filename = filePath.split("/").pop() || "";
531
+ const match = filename.match(/^(?:task-)?\d+(?:\.\d+)?\s*-\s*(.+)\.md$/);
532
+ if (match) {
533
+ return match[1].trim();
534
+ }
535
+ return filename.replace(/\.md$/, "");
536
+ }
537
+ function extractSourceFromPath(filePath) {
538
+ if (filePath.includes("/completed/") || filePath.includes("\\completed\\")) {
539
+ return "completed";
540
+ }
541
+ return "tasks";
542
+ }
543
+ function extractTaskIndexFromPath(filePath) {
544
+ return {
545
+ id: extractIdFromPath(filePath),
546
+ filePath,
547
+ title: extractTitleFromPath(filePath),
548
+ source: extractSourceFromPath(filePath)
549
+ };
550
+ }
529
551
  function escapeRegex(str) {
530
552
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
531
553
  }
532
- const DEFAULT_STATUSES$1 = ["To Do", "In Progress", "Done"];
554
+ const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
533
555
  function parseBacklogConfig(content) {
534
556
  const config = {};
535
557
  const lines = content.split("\n");
@@ -609,10 +631,10 @@ function parseBacklogConfig(content) {
609
631
  }
610
632
  return {
611
633
  projectName: config.projectName || "Backlog",
612
- statuses: config.statuses || [...DEFAULT_STATUSES$1],
634
+ statuses: config.statuses || [...DEFAULT_STATUSES],
613
635
  labels: config.labels || [],
614
636
  milestones: config.milestones || [],
615
- defaultStatus: config.defaultStatus || DEFAULT_STATUSES$1[0],
637
+ defaultStatus: config.defaultStatus || DEFAULT_STATUSES[0],
616
638
  dateFormat: config.dateFormat || "YYYY-MM-DD",
617
639
  defaultAssignee: config.defaultAssignee,
618
640
  defaultReporter: config.defaultReporter,
@@ -747,6 +769,9 @@ class Core {
747
769
  __publicField(this, "config", null);
748
770
  __publicField(this, "tasks", /* @__PURE__ */ new Map());
749
771
  __publicField(this, "initialized", false);
772
+ /** Lightweight task index for lazy loading (no file reads) */
773
+ __publicField(this, "taskIndex", /* @__PURE__ */ new Map());
774
+ __publicField(this, "lazyInitialized", false);
750
775
  this.projectRoot = options.projectRoot;
751
776
  this.fs = options.adapters.fs;
752
777
  }
@@ -828,6 +853,181 @@ class Core {
828
853
  }
829
854
  this.initialized = true;
830
855
  }
856
+ /**
857
+ * Initialize with lazy loading (no file content reads)
858
+ *
859
+ * Only loads config and builds task index from file paths.
860
+ * Task content is loaded on-demand via loadTask().
861
+ * Use this for web/panel contexts where file reads are expensive.
862
+ *
863
+ * @param filePaths - Array of all file paths in the project
864
+ */
865
+ async initializeLazy(filePaths) {
866
+ if (this.lazyInitialized)
867
+ return;
868
+ const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
869
+ const configExists = await this.fs.exists(configPath);
870
+ if (!configExists) {
871
+ throw new Error(`Not a Backlog.md project: config.yml not found at ${configPath}`);
872
+ }
873
+ const configContent = await this.fs.readFile(configPath);
874
+ this.config = parseBacklogConfig(configContent);
875
+ this.taskIndex.clear();
876
+ for (const filePath of filePaths) {
877
+ if (!filePath.endsWith(".md"))
878
+ continue;
879
+ const isTaskFile = filePath.includes("backlog/tasks/") || filePath.includes("backlog\\tasks\\");
880
+ const isCompletedFile = filePath.includes("backlog/completed/") || filePath.includes("backlog\\completed\\");
881
+ if (!isTaskFile && !isCompletedFile)
882
+ continue;
883
+ if (filePath.endsWith("config.yml"))
884
+ continue;
885
+ const indexEntry = extractTaskIndexFromPath(filePath);
886
+ this.taskIndex.set(indexEntry.id, indexEntry);
887
+ }
888
+ this.lazyInitialized = true;
889
+ }
890
+ /**
891
+ * Check if lazy initialization is complete
892
+ */
893
+ isLazyInitialized() {
894
+ return this.lazyInitialized;
895
+ }
896
+ /**
897
+ * Get the task index (lightweight entries)
898
+ */
899
+ getTaskIndex() {
900
+ if (!this.lazyInitialized) {
901
+ throw new Error("Core not lazy initialized. Call initializeLazy() first.");
902
+ }
903
+ return this.taskIndex;
904
+ }
905
+ /**
906
+ * Load a single task by ID (on-demand loading)
907
+ *
908
+ * @param id - Task ID
909
+ * @returns Task or undefined if not found
910
+ */
911
+ async loadTask(id) {
912
+ if (this.tasks.has(id)) {
913
+ return this.tasks.get(id);
914
+ }
915
+ const indexEntry = this.taskIndex.get(id);
916
+ if (!indexEntry) {
917
+ return void 0;
918
+ }
919
+ try {
920
+ const content = await this.fs.readFile(indexEntry.filePath);
921
+ const task = parseTaskMarkdown(content, indexEntry.filePath);
922
+ task.source = indexEntry.source === "completed" ? "completed" : "local";
923
+ this.tasks.set(task.id, task);
924
+ return task;
925
+ } catch (error) {
926
+ console.warn(`Failed to load task ${id} from ${indexEntry.filePath}:`, error);
927
+ return void 0;
928
+ }
929
+ }
930
+ /**
931
+ * Load multiple tasks by ID (on-demand loading)
932
+ *
933
+ * @param ids - Array of task IDs to load
934
+ * @returns Array of loaded tasks (undefined entries filtered out)
935
+ */
936
+ async loadTasks(ids) {
937
+ const tasks = await Promise.all(ids.map((id) => this.loadTask(id)));
938
+ return tasks.filter((t) => t !== void 0);
939
+ }
940
+ /**
941
+ * Get tasks by source (tasks/completed) with pagination
942
+ *
943
+ * This is a lazy-loading alternative to getTasksByStatusPaginated().
944
+ * Groups by directory (tasks/ or completed/) instead of status.
945
+ * Only loads task content for items in the requested page.
946
+ *
947
+ * @param options - Per-source pagination options
948
+ * @returns Paginated tasks grouped by source
949
+ */
950
+ async getTasksBySourcePaginated(options) {
951
+ if (!this.lazyInitialized) {
952
+ throw new Error("Core not lazy initialized. Call initializeLazy() first.");
953
+ }
954
+ const tasksLimit = (options == null ? void 0 : options.tasksLimit) ?? 10;
955
+ const completedLimit = (options == null ? void 0 : options.completedLimit) ?? 10;
956
+ const offset = (options == null ? void 0 : options.offset) ?? 0;
957
+ const tasksSortDirection = (options == null ? void 0 : options.tasksSortDirection) ?? "asc";
958
+ const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
959
+ const sources = ["tasks", "completed"];
960
+ const bySource = /* @__PURE__ */ new Map();
961
+ for (const source of sources) {
962
+ let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
963
+ const limit = source === "tasks" ? tasksLimit : completedLimit;
964
+ if (source === "completed" && completedSortByIdDesc) {
965
+ entries = entries.sort((a, b) => {
966
+ const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
967
+ const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
968
+ return bNum - aNum;
969
+ });
970
+ } else {
971
+ entries = entries.sort((a, b) => {
972
+ const cmp = a.title.localeCompare(b.title);
973
+ return tasksSortDirection === "asc" ? cmp : -cmp;
974
+ });
975
+ }
976
+ const total = entries.length;
977
+ const pageEntries = entries.slice(offset, offset + limit);
978
+ const items = await this.loadTasks(pageEntries.map((e) => e.id));
979
+ bySource.set(source, {
980
+ items,
981
+ total,
982
+ hasMore: offset + limit < total,
983
+ offset,
984
+ limit
985
+ });
986
+ }
987
+ return {
988
+ bySource,
989
+ sources
990
+ };
991
+ }
992
+ /**
993
+ * Load more tasks for a specific source (lazy loading)
994
+ *
995
+ * @param source - Source to load more from ("tasks" or "completed")
996
+ * @param currentOffset - Current offset (items already loaded)
997
+ * @param options - Pagination options
998
+ * @returns Paginated result for the source
999
+ */
1000
+ async loadMoreForSource(source, currentOffset, options) {
1001
+ if (!this.lazyInitialized) {
1002
+ throw new Error("Core not lazy initialized. Call initializeLazy() first.");
1003
+ }
1004
+ const limit = (options == null ? void 0 : options.limit) ?? 10;
1005
+ const sortDirection = (options == null ? void 0 : options.sortDirection) ?? "asc";
1006
+ const completedSortByIdDesc = (options == null ? void 0 : options.completedSortByIdDesc) ?? true;
1007
+ let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
1008
+ if (source === "completed" && completedSortByIdDesc) {
1009
+ entries = entries.sort((a, b) => {
1010
+ const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
1011
+ const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
1012
+ return bNum - aNum;
1013
+ });
1014
+ } else {
1015
+ entries = entries.sort((a, b) => {
1016
+ const cmp = a.title.localeCompare(b.title);
1017
+ return sortDirection === "asc" ? cmp : -cmp;
1018
+ });
1019
+ }
1020
+ const total = entries.length;
1021
+ const pageEntries = entries.slice(currentOffset, currentOffset + limit);
1022
+ const items = await this.loadTasks(pageEntries.map((e) => e.id));
1023
+ return {
1024
+ items,
1025
+ total,
1026
+ hasMore: currentOffset + limit < total,
1027
+ offset: currentOffset,
1028
+ limit
1029
+ };
1030
+ }
831
1031
  /**
832
1032
  * Get the loaded configuration
833
1033
  *
@@ -1155,16 +1355,26 @@ class PanelFileSystemAdapter {
1155
1355
  return path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/");
1156
1356
  }
1157
1357
  }
1158
- const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
1159
- const DEFAULT_PAGE_SIZE = 10;
1358
+ const SOURCE_DISPLAY_LABELS = {
1359
+ tasks: "Active",
1360
+ completed: "Completed"
1361
+ };
1362
+ const DEFAULT_SOURCES = ["tasks", "completed"];
1363
+ const DEFAULT_TASKS_LIMIT = 20;
1364
+ const DEFAULT_COMPLETED_LIMIT = 5;
1160
1365
  function useKanbanData(options) {
1161
- const { context, actions, pageSize = DEFAULT_PAGE_SIZE } = options || {};
1366
+ const {
1367
+ context,
1368
+ actions,
1369
+ tasksLimit = DEFAULT_TASKS_LIMIT,
1370
+ completedLimit = DEFAULT_COMPLETED_LIMIT
1371
+ } = options || {};
1162
1372
  const [tasks, setTasks] = useState([]);
1163
- const [statuses, setStatuses] = useState(DEFAULT_STATUSES);
1373
+ const [sources] = useState(DEFAULT_SOURCES);
1164
1374
  const [isLoading, setIsLoading] = useState(true);
1165
1375
  const [error, setError] = useState(null);
1166
1376
  const [isBacklogProject, setIsBacklogProject] = useState(false);
1167
- const [tasksByStatus, setTasksByStatus] = useState(
1377
+ const [tasksBySource, setTasksBySource] = useState(
1168
1378
  /* @__PURE__ */ new Map()
1169
1379
  );
1170
1380
  const [columnStates, setColumnStates] = useState(
@@ -1213,8 +1423,7 @@ function useKanbanData(options) {
1213
1423
  console.log("[useKanbanData] No context provided");
1214
1424
  setIsBacklogProject(false);
1215
1425
  setTasks([]);
1216
- setStatuses(DEFAULT_STATUSES);
1217
- setTasksByStatus(/* @__PURE__ */ new Map());
1426
+ setTasksBySource(/* @__PURE__ */ new Map());
1218
1427
  setColumnStates(/* @__PURE__ */ new Map());
1219
1428
  setIsLoading(false);
1220
1429
  coreRef.current = null;
@@ -1228,8 +1437,7 @@ function useKanbanData(options) {
1228
1437
  console.log("[useKanbanData] FileTree not available");
1229
1438
  setIsBacklogProject(false);
1230
1439
  setTasks([]);
1231
- setStatuses(DEFAULT_STATUSES);
1232
- setTasksByStatus(/* @__PURE__ */ new Map());
1440
+ setTasksBySource(/* @__PURE__ */ new Map());
1233
1441
  setColumnStates(/* @__PURE__ */ new Map());
1234
1442
  coreRef.current = null;
1235
1443
  return;
@@ -1249,31 +1457,30 @@ function useKanbanData(options) {
1249
1457
  console.log("[useKanbanData] Not a Backlog.md project");
1250
1458
  setIsBacklogProject(false);
1251
1459
  setTasks([]);
1252
- setStatuses(DEFAULT_STATUSES);
1253
- setTasksByStatus(/* @__PURE__ */ new Map());
1460
+ setTasksBySource(/* @__PURE__ */ new Map());
1254
1461
  setColumnStates(/* @__PURE__ */ new Map());
1255
1462
  coreRef.current = null;
1256
1463
  return;
1257
1464
  }
1258
- console.log("[useKanbanData] Loading Backlog.md data with pagination...");
1465
+ console.log("[useKanbanData] Loading Backlog.md data with lazy loading...");
1259
1466
  setIsBacklogProject(true);
1260
- await core.initialize();
1467
+ await core.initializeLazy(filePaths);
1261
1468
  coreRef.current = core;
1262
- const config = core.getConfig();
1263
- const paginatedResult = core.getTasksByStatusPaginated({
1264
- limit: pageSize,
1469
+ const paginatedResult = await core.getTasksBySourcePaginated({
1470
+ tasksLimit,
1471
+ completedLimit,
1265
1472
  offset: 0,
1266
- sortBy: "title",
1267
- sortDirection: "asc"
1473
+ tasksSortDirection: "asc",
1474
+ completedSortByIdDesc: true
1268
1475
  });
1269
- const newTasksByStatus = /* @__PURE__ */ new Map();
1476
+ const newTasksBySource = /* @__PURE__ */ new Map();
1270
1477
  const newColumnStates = /* @__PURE__ */ new Map();
1271
1478
  let allTasks = [];
1272
- for (const status of paginatedResult.statuses) {
1273
- const columnResult = paginatedResult.byStatus.get(status);
1479
+ for (const source of paginatedResult.sources) {
1480
+ const columnResult = paginatedResult.bySource.get(source);
1274
1481
  if (columnResult) {
1275
- newTasksByStatus.set(status, columnResult.items);
1276
- newColumnStates.set(status, {
1482
+ newTasksBySource.set(source, columnResult.items);
1483
+ newColumnStates.set(source, {
1277
1484
  tasks: columnResult.items,
1278
1485
  total: columnResult.total,
1279
1486
  hasMore: columnResult.hasMore,
@@ -1281,8 +1488,8 @@ function useKanbanData(options) {
1281
1488
  });
1282
1489
  allTasks = allTasks.concat(columnResult.items);
1283
1490
  } else {
1284
- newTasksByStatus.set(status, []);
1285
- newColumnStates.set(status, {
1491
+ newTasksBySource.set(source, []);
1492
+ newColumnStates.set(source, {
1286
1493
  tasks: [],
1287
1494
  total: 0,
1288
1495
  hasMore: false,
@@ -1290,72 +1497,71 @@ function useKanbanData(options) {
1290
1497
  });
1291
1498
  }
1292
1499
  }
1293
- const totalTasks = Array.from(paginatedResult.byStatus.values()).reduce(
1500
+ const totalTasks = Array.from(paginatedResult.bySource.values()).reduce(
1294
1501
  (sum, col) => sum + col.total,
1295
1502
  0
1296
1503
  );
1297
1504
  console.log(
1298
- `[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks with ${config.statuses.length} statuses (page size: ${pageSize})`
1505
+ `[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks (active: ${tasksLimit}, completed: ${completedLimit})`
1299
1506
  );
1300
- setStatuses(config.statuses);
1301
1507
  setTasks(allTasks);
1302
- setTasksByStatus(newTasksByStatus);
1508
+ setTasksBySource(newTasksBySource);
1303
1509
  setColumnStates(newColumnStates);
1304
1510
  } catch (err) {
1305
1511
  console.error("[useKanbanData] Failed to load Backlog.md data:", err);
1306
1512
  setError(err instanceof Error ? err.message : "Failed to load backlog data");
1307
1513
  setIsBacklogProject(false);
1308
1514
  setTasks([]);
1309
- setStatuses(DEFAULT_STATUSES);
1310
- setTasksByStatus(/* @__PURE__ */ new Map());
1515
+ setTasksBySource(/* @__PURE__ */ new Map());
1311
1516
  setColumnStates(/* @__PURE__ */ new Map());
1312
1517
  coreRef.current = null;
1313
1518
  } finally {
1314
1519
  setIsLoading(false);
1315
1520
  }
1316
- }, [context, actions, fetchFileContent, pageSize]);
1521
+ }, [context, actions, fetchFileContent, tasksLimit, completedLimit]);
1317
1522
  useEffect(() => {
1318
1523
  loadBacklogData();
1319
1524
  }, [loadBacklogData]);
1320
1525
  const loadMore = useCallback(
1321
- async (status) => {
1526
+ async (source) => {
1322
1527
  const core = coreRef.current;
1323
1528
  if (!core) {
1324
1529
  console.warn("[useKanbanData] Core not available for loadMore");
1325
1530
  return;
1326
1531
  }
1327
- const currentState = columnStates.get(status);
1532
+ const currentState = columnStates.get(source);
1328
1533
  if (!currentState || !currentState.hasMore || currentState.isLoadingMore) {
1329
1534
  return;
1330
1535
  }
1331
1536
  setColumnStates((prev) => {
1332
1537
  const newStates = new Map(prev);
1333
- const state = newStates.get(status);
1538
+ const state = newStates.get(source);
1334
1539
  if (state) {
1335
- newStates.set(status, { ...state, isLoadingMore: true });
1540
+ newStates.set(source, { ...state, isLoadingMore: true });
1336
1541
  }
1337
1542
  return newStates;
1338
1543
  });
1339
1544
  try {
1340
1545
  const currentOffset = currentState.tasks.length;
1341
- const result = core.loadMoreForStatus(
1342
- status,
1546
+ const limit = source === "tasks" ? tasksLimit : completedLimit;
1547
+ const result = await core.loadMoreForSource(
1548
+ source,
1343
1549
  currentOffset,
1344
1550
  {
1345
- limit: pageSize,
1346
- sortBy: "title",
1347
- sortDirection: "asc"
1551
+ limit,
1552
+ sortDirection: "asc",
1553
+ completedSortByIdDesc: source === "completed"
1348
1554
  }
1349
1555
  );
1350
1556
  console.log(
1351
- `[useKanbanData] Loaded ${result.items.length} more tasks for "${status}" (${currentOffset + result.items.length}/${result.total})`
1557
+ `[useKanbanData] Loaded ${result.items.length} more tasks for "${source}" (${currentOffset + result.items.length}/${result.total})`
1352
1558
  );
1353
1559
  setColumnStates((prev) => {
1354
1560
  const newStates = new Map(prev);
1355
- const state = newStates.get(status);
1561
+ const state = newStates.get(source);
1356
1562
  if (state) {
1357
1563
  const newTasks = [...state.tasks, ...result.items];
1358
- newStates.set(status, {
1564
+ newStates.set(source, {
1359
1565
  tasks: newTasks,
1360
1566
  total: result.total,
1361
1567
  hasMore: result.hasMore,
@@ -1364,27 +1570,27 @@ function useKanbanData(options) {
1364
1570
  }
1365
1571
  return newStates;
1366
1572
  });
1367
- setTasksByStatus((prev) => {
1573
+ setTasksBySource((prev) => {
1368
1574
  const newMap = new Map(prev);
1369
- const currentTasks = newMap.get(status) || [];
1370
- newMap.set(status, [...currentTasks, ...result.items]);
1575
+ const currentTasks = newMap.get(source) || [];
1576
+ newMap.set(source, [...currentTasks, ...result.items]);
1371
1577
  return newMap;
1372
1578
  });
1373
1579
  setTasks((prev) => [...prev, ...result.items]);
1374
1580
  } catch (err) {
1375
- console.error(`[useKanbanData] Failed to load more for "${status}":`, err);
1581
+ console.error(`[useKanbanData] Failed to load more for "${source}":`, err);
1376
1582
  setError(err instanceof Error ? err.message : "Failed to load more tasks");
1377
1583
  setColumnStates((prev) => {
1378
1584
  const newStates = new Map(prev);
1379
- const state = newStates.get(status);
1585
+ const state = newStates.get(source);
1380
1586
  if (state) {
1381
- newStates.set(status, { ...state, isLoadingMore: false });
1587
+ newStates.set(source, { ...state, isLoadingMore: false });
1382
1588
  }
1383
1589
  return newStates;
1384
1590
  });
1385
1591
  }
1386
1592
  },
1387
- [columnStates, pageSize]
1593
+ [columnStates, tasksLimit, completedLimit]
1388
1594
  );
1389
1595
  const refreshData = useCallback(async () => {
1390
1596
  await loadBacklogData();
@@ -1401,11 +1607,11 @@ function useKanbanData(options) {
1401
1607
  );
1402
1608
  return {
1403
1609
  tasks,
1404
- statuses,
1610
+ sources,
1405
1611
  isLoading,
1406
1612
  error,
1407
1613
  isBacklogProject,
1408
- tasksByStatus,
1614
+ tasksBySource,
1409
1615
  columnStates,
1410
1616
  loadMore,
1411
1617
  refreshData,
@@ -1944,10 +2150,11 @@ const KanbanPanelContent = ({
1944
2150
  var _a, _b;
1945
2151
  const { theme: theme2 } = useTheme();
1946
2152
  const [_selectedTask, setSelectedTask] = useState(null);
1947
- const { statuses, tasksByStatus, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
2153
+ const { sources, tasksBySource, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
1948
2154
  context,
1949
2155
  actions,
1950
- pageSize: 10
2156
+ tasksLimit: 20,
2157
+ completedLimit: 5
1951
2158
  });
1952
2159
  const handleTaskClick = (task) => {
1953
2160
  setSelectedTask(task);
@@ -2091,21 +2298,21 @@ const KanbanPanelContent = ({
2091
2298
  WebkitOverflowScrolling: "touch"
2092
2299
  // Smooth scrolling on iOS
2093
2300
  },
2094
- children: statuses.map((status) => {
2095
- const columnTasks = tasksByStatus.get(status) || [];
2096
- const columnState = columnStates.get(status);
2301
+ children: sources.map((source) => {
2302
+ const columnTasks = tasksBySource.get(source) || [];
2303
+ const columnState = columnStates.get(source);
2097
2304
  return /* @__PURE__ */ jsx(
2098
2305
  KanbanColumn,
2099
2306
  {
2100
- status,
2307
+ status: SOURCE_DISPLAY_LABELS[source],
2101
2308
  tasks: columnTasks,
2102
2309
  total: columnState == null ? void 0 : columnState.total,
2103
2310
  hasMore: columnState == null ? void 0 : columnState.hasMore,
2104
2311
  isLoadingMore: columnState == null ? void 0 : columnState.isLoadingMore,
2105
- onLoadMore: () => loadMore(status),
2312
+ onLoadMore: () => loadMore(source),
2106
2313
  onTaskClick: handleTaskClick
2107
2314
  },
2108
- status
2315
+ source
2109
2316
  );
2110
2317
  })
2111
2318
  }