@basementuniverse/kanbn 1.1.0 → 1.1.1

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.
package/PLANS.md ADDED
@@ -0,0 +1,9 @@
1
+ # Future plans
2
+
3
+ * multiple boards
4
+ * epics / stories
5
+ * swimlanes
6
+ * list of contributors in index config
7
+ * dropdown in vsce taskeditor
8
+ * optional autopopulate from git user
9
+ * web UI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basementuniverse/kanbn",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "A CLI Kanban application",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -64,7 +64,8 @@ function createChartCell() {
64
64
  return {
65
65
  bg: null,
66
66
  lineMask: 0,
67
- arrowChar: null
67
+ arrowChar: null,
68
+ markerChar: null
68
69
  };
69
70
  }
70
71
 
@@ -121,6 +122,38 @@ function routeDependency(grid, sourceRow, sourceCol, targetRow, targetCol) {
121
122
  grid[targetRow][targetCol].arrowChar = targetCol >= routeCol ? '→' : '←';
122
123
  }
123
124
 
125
+ function setMarker(cell, markerChar) {
126
+ if (!cell.arrowChar && !cell.markerChar) {
127
+ cell.markerChar = markerChar;
128
+ }
129
+ }
130
+
131
+ function routeIncomingExternalDependency(grid, row, targetCol) {
132
+ const arrowCol = Math.max(0, targetCol);
133
+ grid[row][arrowCol].arrowChar = '→';
134
+
135
+ if (arrowCol > 0) {
136
+ addMask(grid[row][arrowCol], DIR_LEFT);
137
+ addMask(grid[row][arrowCol - 1], DIR_RIGHT);
138
+ }
139
+ if (arrowCol > 1) {
140
+ setMarker(grid[row][arrowCol - 2], '…');
141
+ }
142
+ }
143
+
144
+ function routeOutgoingExternalDependency(grid, row, sourceCol, width) {
145
+ const arrowCol = Math.min(width - 1, sourceCol);
146
+ grid[row][arrowCol].arrowChar = '→';
147
+
148
+ if (arrowCol < width - 1) {
149
+ addMask(grid[row][arrowCol], DIR_RIGHT);
150
+ addMask(grid[row][arrowCol + 1], DIR_LEFT);
151
+ }
152
+ if (arrowCol < width - 2) {
153
+ setMarker(grid[row][arrowCol + 2], '…');
154
+ }
155
+ }
156
+
124
157
  function buildDependencyOverlay(tasks, barGeometries, width) {
125
158
  const grid = createChartGrid(tasks.length, width);
126
159
  const taskRowMap = new Map(tasks.map((task, index) => [task.id, index]));
@@ -140,6 +173,14 @@ function buildDependencyOverlay(tasks, barGeometries, width) {
140
173
  barGeometries[targetRow].start
141
174
  );
142
175
  });
176
+
177
+ if (Array.isArray(task.externalIncomingDependencies) && task.externalIncomingDependencies.length > 0) {
178
+ routeIncomingExternalDependency(grid, targetRow, barGeometries[targetRow].start);
179
+ }
180
+
181
+ if (Array.isArray(task.externalOutgoingDependents) && task.externalOutgoingDependents.length > 0) {
182
+ routeOutgoingExternalDependency(grid, targetRow, barGeometries[targetRow].end, width);
183
+ }
143
184
  });
144
185
 
145
186
  return grid;
@@ -165,6 +206,10 @@ function renderChartRow(gridRow, barGeometry, barBackground, nowPosition) {
165
206
  return styleCell(lineChar, ANSI_FG_LINE, bg);
166
207
  }
167
208
 
209
+ if (cell.markerChar) {
210
+ return styleCell(cell.markerChar, ANSI_FG_LINE, bg);
211
+ }
212
+
168
213
  if (nowPosition === column) {
169
214
  return styleCell(NOW_MARKER, ANSI_FG_NOW, bg);
170
215
  }
package/src/main.js CHANGED
@@ -905,14 +905,26 @@ function findDependencyCycle(dependencyMap) {
905
905
  * @param {Date} now
906
906
  * @return {object}
907
907
  */
908
- function buildGanttSchedule(index, tasks, now) {
908
+ function buildGanttSchedule(index, tasks, now, trackedTaskIds = new Set()) {
909
909
  const taskById = new Map(tasks.map((task) => [task.id, task]));
910
910
  const dependencyMap = new Map(tasks.map((task) => [task.id, new Set()]));
911
911
  const dependentsMap = new Map();
912
912
  const indegree = new Map(tasks.map((task) => [task.id, 0]));
913
+ const externalIncomingDependenciesMap = new Map(tasks.map((task) => [task.id, new Set()]));
914
+ const externalOutgoingDependentsMap = new Map(tasks.map((task) => [task.id, new Set()]));
915
+
916
+ const taskExistsInBoard = (taskId) => trackedTaskIds.has(taskId);
913
917
 
914
918
  const addDependencyEdge = (dependencyId, dependentId) => {
919
+ if (!taskById.has(dependentId)) {
920
+ throw new Error(`Task "${dependentId}" depends on missing task "${dependencyId}"`);
921
+ }
922
+
915
923
  if (!taskById.has(dependencyId)) {
924
+ if (taskExistsInBoard(dependencyId)) {
925
+ externalIncomingDependenciesMap.get(dependentId).add(dependencyId);
926
+ return;
927
+ }
916
928
  throw new Error(`Task "${dependentId}" depends on missing task "${dependencyId}"`);
917
929
  }
918
930
 
@@ -939,7 +951,13 @@ function buildGanttSchedule(index, tasks, now) {
939
951
  if (relationType === 'depends-on') {
940
952
  addDependencyEdge(relation.task, task.id);
941
953
  } else if (relationType === 'blocks') {
942
- addDependencyEdge(task.id, relation.task);
954
+ if (taskById.has(relation.task)) {
955
+ addDependencyEdge(task.id, relation.task);
956
+ } else if (taskExistsInBoard(relation.task)) {
957
+ externalOutgoingDependentsMap.get(task.id).add(relation.task);
958
+ } else {
959
+ throw new Error(`Task "${task.id}" blocks missing task "${relation.task}"`);
960
+ }
943
961
  }
944
962
  });
945
963
  });
@@ -1036,6 +1054,8 @@ function buildGanttSchedule(index, tasks, now) {
1036
1054
  const scheduledTask = {
1037
1055
  ...task,
1038
1056
  dependencies,
1057
+ externalIncomingDependencies: [...(externalIncomingDependenciesMap.get(task.id) || [])],
1058
+ externalOutgoingDependents: [...(externalOutgoingDependentsMap.get(task.id) || [])],
1039
1059
  start,
1040
1060
  end,
1041
1061
  blocked: dependencyEnd !== null && preferredStart instanceof Date && dependencyEnd.getTime() > preferredStart.getTime()
@@ -2850,7 +2870,8 @@ class Kanbn {
2850
2870
  }
2851
2871
  );
2852
2872
 
2853
- return buildGanttSchedule(index, tasks, effectiveNow);
2873
+ const trackedTaskIds = getTrackedTaskIds(index);
2874
+ return buildGanttSchedule(index, tasks, effectiveNow, trackedTaskIds);
2854
2875
  }
2855
2876
 
2856
2877
  /**