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

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.
@@ -684,6 +684,28 @@ const PRIORITY_ORDER = {
684
684
  medium: 1,
685
685
  low: 2
686
686
  };
687
+ function sortTasksByTitle(tasks, direction = "asc") {
688
+ return [...tasks].sort((a, b) => {
689
+ const cmp = a.title.localeCompare(b.title);
690
+ return direction === "asc" ? cmp : -cmp;
691
+ });
692
+ }
693
+ function sortTasksBy(tasks, sortBy = "title", direction = "asc") {
694
+ switch (sortBy) {
695
+ case "title":
696
+ return sortTasksByTitle(tasks, direction);
697
+ case "createdDate":
698
+ return [...tasks].sort((a, b) => {
699
+ const cmp = a.createdDate.localeCompare(b.createdDate);
700
+ return direction === "asc" ? cmp : -cmp;
701
+ });
702
+ case "priority":
703
+ case "ordinal":
704
+ default:
705
+ const sorted = sortTasks(tasks);
706
+ return direction === "desc" ? sorted.reverse() : sorted;
707
+ }
708
+ }
687
709
  function sortTasks(tasks) {
688
710
  return [...tasks].sort((a, b) => {
689
711
  if (a.ordinal !== void 0 && b.ordinal !== void 0) {
@@ -863,6 +885,99 @@ class Core {
863
885
  this.ensureInitialized();
864
886
  return this.tasks.get(id);
865
887
  }
888
+ /**
889
+ * List tasks with pagination
890
+ *
891
+ * @param filter - Filter and pagination options
892
+ * @returns Paginated result with tasks
893
+ */
894
+ listTasksPaginated(filter) {
895
+ this.ensureInitialized();
896
+ let tasks = this.applyFilters(Array.from(this.tasks.values()), filter);
897
+ const pagination = (filter == null ? void 0 : filter.pagination) ?? {};
898
+ const sortBy = pagination.sortBy ?? "title";
899
+ const sortDirection = pagination.sortDirection ?? "asc";
900
+ tasks = sortTasksBy(tasks, sortBy, sortDirection);
901
+ const limit = pagination.limit ?? 10;
902
+ const offset = pagination.offset ?? 0;
903
+ const total = tasks.length;
904
+ const items = tasks.slice(offset, offset + limit);
905
+ return {
906
+ items,
907
+ total,
908
+ hasMore: offset + limit < total,
909
+ offset,
910
+ limit
911
+ };
912
+ }
913
+ /**
914
+ * Get tasks by status with pagination per column
915
+ *
916
+ * @param pagination - Pagination options (applied per status)
917
+ * @returns Paginated tasks grouped by status
918
+ */
919
+ getTasksByStatusPaginated(pagination) {
920
+ this.ensureInitialized();
921
+ const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
922
+ const offset = (pagination == null ? void 0 : pagination.offset) ?? 0;
923
+ const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
924
+ const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
925
+ const byStatus = /* @__PURE__ */ new Map();
926
+ const allGrouped = /* @__PURE__ */ new Map();
927
+ for (const status of this.config.statuses) {
928
+ allGrouped.set(status, []);
929
+ }
930
+ for (const task of this.tasks.values()) {
931
+ const list = allGrouped.get(task.status);
932
+ if (list) {
933
+ list.push(task);
934
+ } else {
935
+ allGrouped.set(task.status, [task]);
936
+ }
937
+ }
938
+ for (const status of this.config.statuses) {
939
+ let tasks = allGrouped.get(status) ?? [];
940
+ tasks = sortTasksBy(tasks, sortBy, sortDirection);
941
+ const total = tasks.length;
942
+ const items = tasks.slice(offset, offset + limit);
943
+ byStatus.set(status, {
944
+ items,
945
+ total,
946
+ hasMore: offset + limit < total,
947
+ offset,
948
+ limit
949
+ });
950
+ }
951
+ return {
952
+ byStatus,
953
+ statuses: this.config.statuses
954
+ };
955
+ }
956
+ /**
957
+ * Load more tasks for a specific status
958
+ *
959
+ * @param status - Status column to load more from
960
+ * @param currentOffset - Current offset (items already loaded)
961
+ * @param pagination - Pagination options (limit, sortBy, sortDirection)
962
+ * @returns Paginated result for the status
963
+ */
964
+ loadMoreForStatus(status, currentOffset, pagination) {
965
+ this.ensureInitialized();
966
+ const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
967
+ const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
968
+ const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
969
+ let tasks = Array.from(this.tasks.values()).filter((t) => t.status === status);
970
+ tasks = sortTasksBy(tasks, sortBy, sortDirection);
971
+ const total = tasks.length;
972
+ const items = tasks.slice(currentOffset, currentOffset + limit);
973
+ return {
974
+ items,
975
+ total,
976
+ hasMore: currentOffset + limit < total,
977
+ offset: currentOffset,
978
+ limit
979
+ };
980
+ }
866
981
  /**
867
982
  * Reload all tasks from disk
868
983
  *
@@ -879,6 +994,27 @@ class Core {
879
994
  throw new Error("Core not initialized. Call initialize() first.");
880
995
  }
881
996
  }
997
+ applyFilters(tasks, filter) {
998
+ if (!filter)
999
+ return tasks;
1000
+ let result = tasks;
1001
+ if (filter.status) {
1002
+ result = result.filter((t) => t.status === filter.status);
1003
+ }
1004
+ if (filter.assignee) {
1005
+ result = result.filter((t) => t.assignee.includes(filter.assignee));
1006
+ }
1007
+ if (filter.priority) {
1008
+ result = result.filter((t) => t.priority === filter.priority);
1009
+ }
1010
+ if (filter.labels && filter.labels.length > 0) {
1011
+ result = result.filter((t) => filter.labels.some((label) => t.labels.includes(label)));
1012
+ }
1013
+ if (filter.parentTaskId) {
1014
+ result = result.filter((t) => t.parentTaskId === filter.parentTaskId);
1015
+ }
1016
+ return result;
1017
+ }
882
1018
  async loadTasksFromDirectory(dir, source) {
883
1019
  const entries = await this.fs.readDir(dir);
884
1020
  for (const entry of entries) {
@@ -1020,8 +1156,9 @@ class PanelFileSystemAdapter {
1020
1156
  }
1021
1157
  }
1022
1158
  const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
1159
+ const DEFAULT_PAGE_SIZE = 10;
1023
1160
  function useKanbanData(options) {
1024
- const { context, actions } = options || {};
1161
+ const { context, actions, pageSize = DEFAULT_PAGE_SIZE } = options || {};
1025
1162
  const [tasks, setTasks] = useState([]);
1026
1163
  const [statuses, setStatuses] = useState(DEFAULT_STATUSES);
1027
1164
  const [isLoading, setIsLoading] = useState(true);
@@ -1030,6 +1167,10 @@ function useKanbanData(options) {
1030
1167
  const [tasksByStatus, setTasksByStatus] = useState(
1031
1168
  /* @__PURE__ */ new Map()
1032
1169
  );
1170
+ const [columnStates, setColumnStates] = useState(
1171
+ /* @__PURE__ */ new Map()
1172
+ );
1173
+ const coreRef = useRef(null);
1033
1174
  const activeFilePathRef = useRef(null);
1034
1175
  const contextRef = useRef(context);
1035
1176
  const actionsRef = useRef(actions);
@@ -1074,22 +1215,26 @@ function useKanbanData(options) {
1074
1215
  setTasks([]);
1075
1216
  setStatuses(DEFAULT_STATUSES);
1076
1217
  setTasksByStatus(/* @__PURE__ */ new Map());
1218
+ setColumnStates(/* @__PURE__ */ new Map());
1077
1219
  setIsLoading(false);
1220
+ coreRef.current = null;
1078
1221
  return;
1079
1222
  }
1080
1223
  setIsLoading(true);
1081
1224
  setError(null);
1082
1225
  try {
1083
1226
  const fileTreeSlice = context.getRepositorySlice("fileTree");
1084
- if (!((_a = fileTreeSlice == null ? void 0 : fileTreeSlice.data) == null ? void 0 : _a.files)) {
1227
+ if (!((_a = fileTreeSlice == null ? void 0 : fileTreeSlice.data) == null ? void 0 : _a.allFiles)) {
1085
1228
  console.log("[useKanbanData] FileTree not available");
1086
1229
  setIsBacklogProject(false);
1087
1230
  setTasks([]);
1088
1231
  setStatuses(DEFAULT_STATUSES);
1089
1232
  setTasksByStatus(/* @__PURE__ */ new Map());
1233
+ setColumnStates(/* @__PURE__ */ new Map());
1234
+ coreRef.current = null;
1090
1235
  return;
1091
1236
  }
1092
- const files = fileTreeSlice.data.files;
1237
+ const files = fileTreeSlice.data.allFiles;
1093
1238
  const filePaths = files.map((f) => f.path);
1094
1239
  const fs = new PanelFileSystemAdapter({
1095
1240
  fetchFile: fetchFileContent,
@@ -1106,20 +1251,56 @@ function useKanbanData(options) {
1106
1251
  setTasks([]);
1107
1252
  setStatuses(DEFAULT_STATUSES);
1108
1253
  setTasksByStatus(/* @__PURE__ */ new Map());
1254
+ setColumnStates(/* @__PURE__ */ new Map());
1255
+ coreRef.current = null;
1109
1256
  return;
1110
1257
  }
1111
- console.log("[useKanbanData] Loading Backlog.md data...");
1258
+ console.log("[useKanbanData] Loading Backlog.md data with pagination...");
1112
1259
  setIsBacklogProject(true);
1113
1260
  await core.initialize();
1261
+ coreRef.current = core;
1114
1262
  const config = core.getConfig();
1115
- const grouped = core.getTasksByStatus();
1116
- const allTasks = core.listTasks();
1263
+ const paginatedResult = core.getTasksByStatusPaginated({
1264
+ limit: pageSize,
1265
+ offset: 0,
1266
+ sortBy: "title",
1267
+ sortDirection: "asc"
1268
+ });
1269
+ const newTasksByStatus = /* @__PURE__ */ new Map();
1270
+ const newColumnStates = /* @__PURE__ */ new Map();
1271
+ let allTasks = [];
1272
+ for (const status of paginatedResult.statuses) {
1273
+ const columnResult = paginatedResult.byStatus.get(status);
1274
+ if (columnResult) {
1275
+ newTasksByStatus.set(status, columnResult.items);
1276
+ newColumnStates.set(status, {
1277
+ tasks: columnResult.items,
1278
+ total: columnResult.total,
1279
+ hasMore: columnResult.hasMore,
1280
+ isLoadingMore: false
1281
+ });
1282
+ allTasks = allTasks.concat(columnResult.items);
1283
+ } else {
1284
+ newTasksByStatus.set(status, []);
1285
+ newColumnStates.set(status, {
1286
+ tasks: [],
1287
+ total: 0,
1288
+ hasMore: false,
1289
+ isLoadingMore: false
1290
+ });
1291
+ }
1292
+ }
1293
+ const totalTasks = Array.from(paginatedResult.byStatus.values()).reduce(
1294
+ (sum, col) => sum + col.total,
1295
+ 0
1296
+ );
1117
1297
  console.log(
1118
- `[useKanbanData] Loaded ${allTasks.length} tasks with ${config.statuses.length} statuses`
1298
+ `[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks with ${config.statuses.length} statuses (page size: ${pageSize})`
1119
1299
  );
1120
1300
  setStatuses(config.statuses);
1121
1301
  setTasks(allTasks);
1122
- setTasksByStatus(grouped);
1302
+ setTasksByStatus(newTasksByStatus);
1303
+ setColumnStates(newColumnStates);
1123
1304
  } catch (err) {
1124
1305
  console.error("[useKanbanData] Failed to load Backlog.md data:", err);
1125
1306
  setError(err instanceof Error ? err.message : "Failed to load backlog data");
@@ -1127,13 +1308,84 @@ function useKanbanData(options) {
1127
1308
  setTasks([]);
1128
1309
  setStatuses(DEFAULT_STATUSES);
1129
1310
  setTasksByStatus(/* @__PURE__ */ new Map());
1311
+ setColumnStates(/* @__PURE__ */ new Map());
1312
+ coreRef.current = null;
1130
1313
  } finally {
1131
1314
  setIsLoading(false);
1132
1315
  }
1133
- }, [context, actions, fetchFileContent]);
1316
+ }, [context, actions, fetchFileContent, pageSize]);
1134
1317
  useEffect(() => {
1135
1318
  loadBacklogData();
1136
1319
  }, [loadBacklogData]);
1320
+ const loadMore = useCallback(
1321
+ async (status) => {
1322
+ const core = coreRef.current;
1323
+ if (!core) {
1324
+ console.warn("[useKanbanData] Core not available for loadMore");
1325
+ return;
1326
+ }
1327
+ const currentState = columnStates.get(status);
1328
+ if (!currentState || !currentState.hasMore || currentState.isLoadingMore) {
1329
+ return;
1330
+ }
1331
+ setColumnStates((prev) => {
1332
+ const newStates = new Map(prev);
1333
+ const state = newStates.get(status);
1334
+ if (state) {
1335
+ newStates.set(status, { ...state, isLoadingMore: true });
1336
+ }
1337
+ return newStates;
1338
+ });
1339
+ try {
1340
+ const currentOffset = currentState.tasks.length;
1341
+ const result = core.loadMoreForStatus(
1342
+ status,
1343
+ currentOffset,
1344
+ {
1345
+ limit: pageSize,
1346
+ sortBy: "title",
1347
+ sortDirection: "asc"
1348
+ }
1349
+ );
1350
+ console.log(
1351
+ `[useKanbanData] Loaded ${result.items.length} more tasks for "${status}" (${currentOffset + result.items.length}/${result.total})`
1352
+ );
1353
+ setColumnStates((prev) => {
1354
+ const newStates = new Map(prev);
1355
+ const state = newStates.get(status);
1356
+ if (state) {
1357
+ const newTasks = [...state.tasks, ...result.items];
1358
+ newStates.set(status, {
1359
+ tasks: newTasks,
1360
+ total: result.total,
1361
+ hasMore: result.hasMore,
1362
+ isLoadingMore: false
1363
+ });
1364
+ }
1365
+ return newStates;
1366
+ });
1367
+ setTasksByStatus((prev) => {
1368
+ const newMap = new Map(prev);
1369
+ const currentTasks = newMap.get(status) || [];
1370
+ newMap.set(status, [...currentTasks, ...result.items]);
1371
+ return newMap;
1372
+ });
1373
+ setTasks((prev) => [...prev, ...result.items]);
1374
+ } catch (err) {
1375
+ console.error(`[useKanbanData] Failed to load more for "${status}":`, err);
1376
+ setError(err instanceof Error ? err.message : "Failed to load more tasks");
1377
+ setColumnStates((prev) => {
1378
+ const newStates = new Map(prev);
1379
+ const state = newStates.get(status);
1380
+ if (state) {
1381
+ newStates.set(status, { ...state, isLoadingMore: false });
1382
+ }
1383
+ return newStates;
1384
+ });
1385
+ }
1386
+ },
1387
+ [columnStates, pageSize]
1388
+ );
1137
1389
  const refreshData = useCallback(async () => {
1138
1390
  await loadBacklogData();
1139
1391
  }, [loadBacklogData]);
@@ -1154,6 +1406,8 @@ function useKanbanData(options) {
1154
1406
  error,
1155
1407
  isBacklogProject,
1156
1408
  tasksByStatus,
1409
+ columnStates,
1410
+ loadMore,
1157
1411
  refreshData,
1158
1412
  updateTaskStatus
1159
1413
  };
@@ -1161,6 +1415,10 @@ function useKanbanData(options) {
1161
1415
  const KanbanColumn = ({
1162
1416
  status,
1163
1417
  tasks,
1418
+ total,
1419
+ hasMore = false,
1420
+ isLoadingMore = false,
1421
+ onLoadMore,
1164
1422
  onTaskClick
1165
1423
  }) => {
1166
1424
  const { theme: theme2 } = useTheme();
@@ -1176,6 +1434,7 @@ const KanbanColumn = ({
1176
1434
  return theme2.colors.border;
1177
1435
  }
1178
1436
  };
1437
+ const remaining = total !== void 0 ? total - tasks.length : 0;
1179
1438
  return /* @__PURE__ */ jsxs(
1180
1439
  "div",
1181
1440
  {
@@ -1224,13 +1483,13 @@ const KanbanColumn = ({
1224
1483
  padding: "2px 8px",
1225
1484
  borderRadius: theme2.radii[1]
1226
1485
  },
1227
- children: tasks.length
1486
+ children: total !== void 0 ? `${tasks.length}/${total}` : tasks.length
1228
1487
  }
1229
1488
  )
1230
1489
  ]
1231
1490
  }
1232
1491
  ),
1233
- /* @__PURE__ */ jsx(
1492
+ /* @__PURE__ */ jsxs(
1234
1493
  "div",
1235
1494
  {
1236
1495
  style: {
@@ -1241,131 +1500,190 @@ const KanbanColumn = ({
1241
1500
  overflowY: "auto",
1242
1501
  WebkitOverflowScrolling: "touch"
1243
1502
  },
1244
- children: tasks.map((task) => /* @__PURE__ */ jsxs(
1245
- "div",
1246
- {
1247
- onClick: () => onTaskClick == null ? void 0 : onTaskClick(task),
1248
- style: {
1249
- background: theme2.colors.surface,
1250
- borderRadius: theme2.radii[2],
1251
- padding: "12px",
1252
- border: `1px solid ${theme2.colors.border}`,
1253
- borderLeft: `4px solid ${getPriorityColor(task.priority)}`,
1254
- cursor: onTaskClick ? "pointer" : "default",
1255
- transition: "all 0.2s ease",
1256
- minHeight: "44px"
1257
- // Minimum touch target size for mobile
1258
- },
1259
- onMouseEnter: (e) => {
1260
- if (onTaskClick) {
1261
- e.currentTarget.style.transform = "translateY(-2px)";
1262
- e.currentTarget.style.boxShadow = `0 4px 8px ${theme2.colors.border}`;
1263
- }
1264
- },
1265
- onMouseLeave: (e) => {
1266
- if (onTaskClick) {
1267
- e.currentTarget.style.transform = "translateY(0)";
1268
- e.currentTarget.style.boxShadow = "none";
1269
- }
1270
- },
1271
- children: [
1272
- /* @__PURE__ */ jsx(
1273
- "h4",
1274
- {
1275
- style: {
1276
- margin: "0 0 8px 0",
1277
- fontSize: theme2.fontSizes[2],
1278
- color: theme2.colors.text,
1279
- fontWeight: theme2.fontWeights.medium
1280
- },
1281
- children: task.title
1503
+ children: [
1504
+ tasks.map((task) => /* @__PURE__ */ jsxs(
1505
+ "div",
1506
+ {
1507
+ onClick: () => onTaskClick == null ? void 0 : onTaskClick(task),
1508
+ style: {
1509
+ background: theme2.colors.surface,
1510
+ borderRadius: theme2.radii[2],
1511
+ padding: "12px",
1512
+ border: `1px solid ${theme2.colors.border}`,
1513
+ borderLeft: `4px solid ${getPriorityColor(task.priority)}`,
1514
+ cursor: onTaskClick ? "pointer" : "default",
1515
+ transition: "all 0.2s ease",
1516
+ minHeight: "44px"
1517
+ // Minimum touch target size for mobile
1518
+ },
1519
+ onMouseEnter: (e) => {
1520
+ if (onTaskClick) {
1521
+ e.currentTarget.style.transform = "translateY(-2px)";
1522
+ e.currentTarget.style.boxShadow = `0 4px 8px ${theme2.colors.border}`;
1282
1523
  }
1283
- ),
1284
- task.description && /* @__PURE__ */ jsx(
1285
- "p",
1286
- {
1287
- style: {
1288
- margin: "0 0 8px 0",
1289
- fontSize: theme2.fontSizes[1],
1290
- color: theme2.colors.textSecondary,
1291
- overflow: "hidden",
1292
- textOverflow: "ellipsis",
1293
- display: "-webkit-box",
1294
- WebkitLineClamp: 2,
1295
- WebkitBoxOrient: "vertical",
1296
- lineHeight: "1.4"
1297
- },
1298
- children: task.description
1524
+ },
1525
+ onMouseLeave: (e) => {
1526
+ if (onTaskClick) {
1527
+ e.currentTarget.style.transform = "translateY(0)";
1528
+ e.currentTarget.style.boxShadow = "none";
1299
1529
  }
1300
- ),
1301
- task.labels && task.labels.length > 0 && /* @__PURE__ */ jsx(
1302
- "div",
1303
- {
1304
- style: {
1305
- display: "flex",
1306
- gap: "4px",
1307
- flexWrap: "wrap",
1308
- marginBottom: "8px"
1309
- },
1310
- children: task.labels.map((label) => /* @__PURE__ */ jsx(
1311
- "span",
1312
- {
1313
- style: {
1314
- fontSize: theme2.fontSizes[0],
1315
- color: theme2.colors.primary,
1316
- background: `${theme2.colors.primary}20`,
1317
- padding: "2px 8px",
1318
- borderRadius: theme2.radii[1],
1319
- fontWeight: theme2.fontWeights.medium
1320
- },
1321
- children: label
1530
+ },
1531
+ children: [
1532
+ /* @__PURE__ */ jsx(
1533
+ "h4",
1534
+ {
1535
+ style: {
1536
+ margin: "0 0 8px 0",
1537
+ fontSize: theme2.fontSizes[2],
1538
+ color: theme2.colors.text,
1539
+ fontWeight: theme2.fontWeights.medium
1322
1540
  },
1323
- label
1324
- ))
1325
- }
1326
- ),
1327
- /* @__PURE__ */ jsxs(
1328
- "div",
1329
- {
1330
- style: {
1331
- display: "flex",
1332
- alignItems: "center",
1333
- justifyContent: "space-between",
1334
- fontSize: theme2.fontSizes[0],
1335
- color: theme2.colors.textMuted
1336
- },
1337
- children: [
1338
- /* @__PURE__ */ jsx(
1339
- "span",
1340
- {
1341
- style: {
1342
- fontFamily: theme2.fonts.monospace
1343
- },
1344
- children: task.id
1345
- }
1346
- ),
1347
- task.assignee && task.assignee.length > 0 && /* @__PURE__ */ jsxs(
1541
+ children: task.title
1542
+ }
1543
+ ),
1544
+ task.description && /* @__PURE__ */ jsx(
1545
+ "p",
1546
+ {
1547
+ style: {
1548
+ margin: "0 0 8px 0",
1549
+ fontSize: theme2.fontSizes[1],
1550
+ color: theme2.colors.textSecondary,
1551
+ overflow: "hidden",
1552
+ textOverflow: "ellipsis",
1553
+ display: "-webkit-box",
1554
+ WebkitLineClamp: 2,
1555
+ WebkitBoxOrient: "vertical",
1556
+ lineHeight: "1.4"
1557
+ },
1558
+ children: task.description
1559
+ }
1560
+ ),
1561
+ task.labels && task.labels.length > 0 && /* @__PURE__ */ jsx(
1562
+ "div",
1563
+ {
1564
+ style: {
1565
+ display: "flex",
1566
+ gap: "4px",
1567
+ flexWrap: "wrap",
1568
+ marginBottom: "8px"
1569
+ },
1570
+ children: task.labels.map((label) => /* @__PURE__ */ jsx(
1348
1571
  "span",
1349
1572
  {
1350
1573
  style: {
1351
- color: theme2.colors.textSecondary
1574
+ fontSize: theme2.fontSizes[0],
1575
+ color: theme2.colors.primary,
1576
+ background: `${theme2.colors.primary}20`,
1577
+ padding: "2px 8px",
1578
+ borderRadius: theme2.radii[1],
1579
+ fontWeight: theme2.fontWeights.medium
1352
1580
  },
1353
- children: [
1354
- task.assignee.length,
1355
- " assignee",
1356
- task.assignee.length !== 1 ? "s" : ""
1357
- ]
1358
- }
1359
- )
1360
- ]
1581
+ children: label
1582
+ },
1583
+ label
1584
+ ))
1585
+ }
1586
+ ),
1587
+ /* @__PURE__ */ jsxs(
1588
+ "div",
1589
+ {
1590
+ style: {
1591
+ display: "flex",
1592
+ alignItems: "center",
1593
+ justifyContent: "space-between",
1594
+ fontSize: theme2.fontSizes[0],
1595
+ color: theme2.colors.textMuted
1596
+ },
1597
+ children: [
1598
+ /* @__PURE__ */ jsx(
1599
+ "span",
1600
+ {
1601
+ style: {
1602
+ fontFamily: theme2.fonts.monospace
1603
+ },
1604
+ children: task.id
1605
+ }
1606
+ ),
1607
+ task.assignee && task.assignee.length > 0 && /* @__PURE__ */ jsxs(
1608
+ "span",
1609
+ {
1610
+ style: {
1611
+ color: theme2.colors.textSecondary
1612
+ },
1613
+ children: [
1614
+ task.assignee.length,
1615
+ " assignee",
1616
+ task.assignee.length !== 1 ? "s" : ""
1617
+ ]
1618
+ }
1619
+ )
1620
+ ]
1621
+ }
1622
+ )
1623
+ ]
1624
+ },
1625
+ task.id
1626
+ )),
1627
+ hasMore && onLoadMore && /* @__PURE__ */ jsx(
1628
+ "button",
1629
+ {
1630
+ onClick: onLoadMore,
1631
+ disabled: isLoadingMore,
1632
+ style: {
1633
+ background: theme2.colors.background,
1634
+ border: `1px dashed ${theme2.colors.border}`,
1635
+ borderRadius: theme2.radii[2],
1636
+ padding: "12px",
1637
+ cursor: isLoadingMore ? "wait" : "pointer",
1638
+ color: theme2.colors.textSecondary,
1639
+ fontSize: theme2.fontSizes[1],
1640
+ fontWeight: theme2.fontWeights.medium,
1641
+ transition: "all 0.2s ease",
1642
+ minHeight: "44px",
1643
+ display: "flex",
1644
+ alignItems: "center",
1645
+ justifyContent: "center",
1646
+ gap: "8px"
1647
+ },
1648
+ onMouseEnter: (e) => {
1649
+ if (!isLoadingMore) {
1650
+ e.currentTarget.style.background = theme2.colors.backgroundSecondary;
1651
+ e.currentTarget.style.borderColor = theme2.colors.primary;
1652
+ e.currentTarget.style.color = theme2.colors.primary;
1361
1653
  }
1362
- )
1363
- ]
1364
- },
1365
- task.id
1366
- ))
1654
+ },
1655
+ onMouseLeave: (e) => {
1656
+ e.currentTarget.style.background = theme2.colors.background;
1657
+ e.currentTarget.style.borderColor = theme2.colors.border;
1658
+ e.currentTarget.style.color = theme2.colors.textSecondary;
1659
+ },
1660
+ children: isLoadingMore ? /* @__PURE__ */ jsxs(Fragment, { children: [
1661
+ /* @__PURE__ */ jsx(
1662
+ "span",
1663
+ {
1664
+ style: {
1665
+ display: "inline-block",
1666
+ width: "14px",
1667
+ height: "14px",
1668
+ border: `2px solid ${theme2.colors.border}`,
1669
+ borderTopColor: theme2.colors.primary,
1670
+ borderRadius: "50%",
1671
+ animation: "spin 1s linear infinite"
1672
+ }
1673
+ }
1674
+ ),
1675
+ "Loading..."
1676
+ ] }) : `Load more (${remaining} remaining)`
1677
+ }
1678
+ )
1679
+ ]
1367
1680
  }
1368
- )
1681
+ ),
1682
+ /* @__PURE__ */ jsx("style", { children: `
1683
+ @keyframes spin {
1684
+ to { transform: rotate(360deg); }
1685
+ }
1686
+ ` })
1369
1687
  ]
1370
1688
  }
1371
1689
  );
@@ -1626,9 +1944,10 @@ const KanbanPanelContent = ({
1626
1944
  var _a, _b;
1627
1945
  const { theme: theme2 } = useTheme();
1628
1946
  const [_selectedTask, setSelectedTask] = useState(null);
1629
- const { statuses, tasksByStatus, error, isBacklogProject, refreshData } = useKanbanData({
1947
+ const { statuses, tasksByStatus, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
1630
1948
  context,
1631
- actions
1949
+ actions,
1950
+ pageSize: 10
1632
1951
  });
1633
1952
  const handleTaskClick = (task) => {
1634
1953
  setSelectedTask(task);
@@ -1774,11 +2093,16 @@ const KanbanPanelContent = ({
1774
2093
  },
1775
2094
  children: statuses.map((status) => {
1776
2095
  const columnTasks = tasksByStatus.get(status) || [];
2096
+ const columnState = columnStates.get(status);
1777
2097
  return /* @__PURE__ */ jsx(
1778
2098
  KanbanColumn,
1779
2099
  {
1780
2100
  status,
1781
2101
  tasks: columnTasks,
2102
+ total: columnState == null ? void 0 : columnState.total,
2103
+ hasMore: columnState == null ? void 0 : columnState.hasMore,
2104
+ isLoadingMore: columnState == null ? void 0 : columnState.isLoadingMore,
2105
+ onLoadMore: () => loadMore(status),
1782
2106
  onTaskClick: handleTaskClick
1783
2107
  },
1784
2108
  status