kandown 0.17.0 → 0.17.2
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/LICENSE +1 -1
- package/README.md +4 -2
- package/bin/kandown.js +381 -130
- package/bin/tui.js +367 -189
- package/dist/index.html +125 -125
- package/package.json +5 -2
package/bin/tui.js
CHANGED
|
@@ -53985,8 +53985,26 @@ var import_react38 = __toESM(require_react(), 1);
|
|
|
53985
53985
|
var import_react34 = __toESM(require_react(), 1);
|
|
53986
53986
|
|
|
53987
53987
|
// src/cli/lib/config.ts
|
|
53988
|
-
import { readFileSync as readFileSync2,
|
|
53988
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
53989
53989
|
import { join } from "path";
|
|
53990
|
+
|
|
53991
|
+
// src/cli/lib/atomic-write.ts
|
|
53992
|
+
import { renameSync, unlinkSync, writeFileSync } from "fs";
|
|
53993
|
+
function atomicWriteFileSync(path, content) {
|
|
53994
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
53995
|
+
try {
|
|
53996
|
+
writeFileSync(tmp, content, "utf8");
|
|
53997
|
+
renameSync(tmp, path);
|
|
53998
|
+
} catch (e) {
|
|
53999
|
+
try {
|
|
54000
|
+
unlinkSync(tmp);
|
|
54001
|
+
} catch {
|
|
54002
|
+
}
|
|
54003
|
+
throw e;
|
|
54004
|
+
}
|
|
54005
|
+
}
|
|
54006
|
+
|
|
54007
|
+
// src/cli/lib/config.ts
|
|
53990
54008
|
var DEFAULT_CONFIG = {
|
|
53991
54009
|
ui: { language: "en", theme: "auto", skin: "kandown", font: "inter" },
|
|
53992
54010
|
agent: { suggestFollowUp: false, maxSuggestions: 3 },
|
|
@@ -54051,7 +54069,7 @@ function loadConfig(kandownDir) {
|
|
|
54051
54069
|
}
|
|
54052
54070
|
function saveConfig(kandownDir, config) {
|
|
54053
54071
|
const configPath = join(kandownDir, "kandown.json");
|
|
54054
|
-
|
|
54072
|
+
atomicWriteFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
54055
54073
|
}
|
|
54056
54074
|
function getConfigValue(config, path) {
|
|
54057
54075
|
const parts = path.split(".");
|
|
@@ -54359,7 +54377,7 @@ function ValueDisplay({ setting, value, focused }) {
|
|
|
54359
54377
|
var import_react37 = __toESM(require_react(), 1);
|
|
54360
54378
|
|
|
54361
54379
|
// src/cli/lib/board-reader.ts
|
|
54362
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3
|
|
54380
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
54363
54381
|
import { dirname, join as join2 } from "path";
|
|
54364
54382
|
|
|
54365
54383
|
// src/lib/types.ts
|
|
@@ -54635,11 +54653,11 @@ function moveTaskToColumn(kandownDir, taskId, targetColumn) {
|
|
|
54635
54653
|
if (!existsSync3(taskPath)) return false;
|
|
54636
54654
|
try {
|
|
54637
54655
|
const parsed = readTask(kandownDir, taskId);
|
|
54638
|
-
|
|
54656
|
+
atomicWriteFileSync(taskPath, serializeTaskFile({
|
|
54639
54657
|
...parsed.frontmatter,
|
|
54640
54658
|
id: taskId,
|
|
54641
54659
|
status: targetColumn
|
|
54642
|
-
}, parsed.body)
|
|
54660
|
+
}, parsed.body));
|
|
54643
54661
|
return true;
|
|
54644
54662
|
} catch (e) {
|
|
54645
54663
|
console.error(`[kandown] Failed to move task ${taskId} to ${targetColumn}:`, e.message);
|
|
@@ -54648,9 +54666,9 @@ function moveTaskToColumn(kandownDir, taskId, targetColumn) {
|
|
|
54648
54666
|
}
|
|
54649
54667
|
|
|
54650
54668
|
// src/cli/lib/daemon.ts
|
|
54651
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
54669
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
54652
54670
|
import { dirname as dirname2, join as join3 } from "path";
|
|
54653
|
-
import { spawn } from "child_process";
|
|
54671
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
54654
54672
|
import { createConnection } from "net";
|
|
54655
54673
|
function metadataPath(kandownDir) {
|
|
54656
54674
|
return join3(kandownDir, "daemon.json");
|
|
@@ -54660,13 +54678,14 @@ function isRecord(value) {
|
|
|
54660
54678
|
}
|
|
54661
54679
|
function parseMetadata(value) {
|
|
54662
54680
|
if (!isRecord(value)) return null;
|
|
54663
|
-
const { pid, port, url, kandownDir, startedAt, version } = value;
|
|
54681
|
+
const { pid, port, url, kandownDir, startedAt, version, token } = value;
|
|
54664
54682
|
if (typeof pid !== "number" || !Number.isInteger(pid)) return null;
|
|
54665
54683
|
if (typeof port !== "number" || !Number.isInteger(port)) return null;
|
|
54666
54684
|
if (typeof url !== "string" || typeof kandownDir !== "string") return null;
|
|
54667
54685
|
if (typeof startedAt !== "string") return null;
|
|
54668
54686
|
if (version !== null && typeof version !== "string" && version !== void 0) return null;
|
|
54669
|
-
|
|
54687
|
+
if (token !== null && typeof token !== "string" && token !== void 0) return null;
|
|
54688
|
+
return { pid, port, url, kandownDir, startedAt, version: version ?? null, token: typeof token === "string" ? token : null };
|
|
54670
54689
|
}
|
|
54671
54690
|
function readDaemonMetadata(kandownDir) {
|
|
54672
54691
|
const path = metadataPath(kandownDir);
|
|
@@ -54680,7 +54699,7 @@ function readDaemonMetadata(kandownDir) {
|
|
|
54680
54699
|
function removeDaemonMetadata(kandownDir) {
|
|
54681
54700
|
try {
|
|
54682
54701
|
const path = metadataPath(kandownDir);
|
|
54683
|
-
if (existsSync4(path))
|
|
54702
|
+
if (existsSync4(path)) unlinkSync2(path);
|
|
54684
54703
|
} catch {
|
|
54685
54704
|
}
|
|
54686
54705
|
}
|
|
@@ -54776,20 +54795,45 @@ async function startProjectDaemon(kandownDir, preferredPort) {
|
|
|
54776
54795
|
child.unref();
|
|
54777
54796
|
return waitForDaemon(kandownDir);
|
|
54778
54797
|
}
|
|
54798
|
+
async function isOwnedKandownDaemon(pid, port, kandownDir) {
|
|
54799
|
+
const remote = await fetchDaemonInfo(port);
|
|
54800
|
+
if (remote) return remote.pid === pid && remote.kandownDir === kandownDir;
|
|
54801
|
+
try {
|
|
54802
|
+
const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], {
|
|
54803
|
+
encoding: "utf8",
|
|
54804
|
+
timeout: 2e3
|
|
54805
|
+
}).trim();
|
|
54806
|
+
return /kandown/.test(cmd) && cmd.includes(kandownDir);
|
|
54807
|
+
} catch {
|
|
54808
|
+
return false;
|
|
54809
|
+
}
|
|
54810
|
+
}
|
|
54779
54811
|
async function stopProjectDaemon(kandownDir) {
|
|
54780
|
-
const
|
|
54781
|
-
if (!
|
|
54812
|
+
const metadata = readDaemonMetadata(kandownDir);
|
|
54813
|
+
if (!metadata) return false;
|
|
54814
|
+
const pid = metadata.pid;
|
|
54815
|
+
if (!isProcessAlive(pid)) {
|
|
54816
|
+
removeDaemonMetadata(kandownDir);
|
|
54817
|
+
return false;
|
|
54818
|
+
}
|
|
54819
|
+
if (!await isOwnedKandownDaemon(pid, metadata.port, kandownDir)) {
|
|
54782
54820
|
removeDaemonMetadata(kandownDir);
|
|
54783
54821
|
return false;
|
|
54784
54822
|
}
|
|
54785
54823
|
try {
|
|
54786
|
-
process.kill(
|
|
54824
|
+
process.kill(pid, "SIGTERM");
|
|
54787
54825
|
} catch {
|
|
54788
54826
|
}
|
|
54789
54827
|
const started = Date.now();
|
|
54790
|
-
while (Date.now() - started < 2500 && isProcessAlive(
|
|
54828
|
+
while (Date.now() - started < 2500 && isProcessAlive(pid)) {
|
|
54791
54829
|
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
54792
54830
|
}
|
|
54831
|
+
if (isProcessAlive(pid)) {
|
|
54832
|
+
try {
|
|
54833
|
+
process.kill(pid, "SIGKILL");
|
|
54834
|
+
} catch {
|
|
54835
|
+
}
|
|
54836
|
+
}
|
|
54793
54837
|
removeDaemonMetadata(kandownDir);
|
|
54794
54838
|
return true;
|
|
54795
54839
|
}
|
|
@@ -56700,7 +56744,7 @@ function createWatcher() {
|
|
|
56700
56744
|
}
|
|
56701
56745
|
|
|
56702
56746
|
// src/cli/lib/agents.ts
|
|
56703
|
-
import { execFileSync as
|
|
56747
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
56704
56748
|
var AGENTS = [
|
|
56705
56749
|
{
|
|
56706
56750
|
id: "claude",
|
|
@@ -56797,7 +56841,7 @@ var installCache = /* @__PURE__ */ new Map();
|
|
|
56797
56841
|
function isAgentInstalled(bin) {
|
|
56798
56842
|
if (installCache.has(bin)) return installCache.get(bin);
|
|
56799
56843
|
try {
|
|
56800
|
-
|
|
56844
|
+
execFileSync3("which", [bin], { stdio: "ignore" });
|
|
56801
56845
|
installCache.set(bin, true);
|
|
56802
56846
|
return true;
|
|
56803
56847
|
} catch {
|
|
@@ -56834,7 +56878,7 @@ function buildPrompt(agentDoc, taskContent, taskId, kandownDir) {
|
|
|
56834
56878
|
|
|
56835
56879
|
// src/cli/lib/launcher.ts
|
|
56836
56880
|
import { execSync, spawn as spawn2 } from "child_process";
|
|
56837
|
-
import { writeFileSync as
|
|
56881
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
56838
56882
|
import { join as join7 } from "path";
|
|
56839
56883
|
import { tmpdir } from "os";
|
|
56840
56884
|
function isInTmux() {
|
|
@@ -56870,7 +56914,7 @@ function launchAgent(opts) {
|
|
|
56870
56914
|
}
|
|
56871
56915
|
const contextFile = join7(tmpdir(), `kandown-${taskId}-context.md`);
|
|
56872
56916
|
try {
|
|
56873
|
-
|
|
56917
|
+
writeFileSync2(contextFile, `${systemPrompt}
|
|
56874
56918
|
|
|
56875
56919
|
---
|
|
56876
56920
|
|
|
@@ -57224,6 +57268,24 @@ function MovePlaceholder({ name, focused, colWidth }) {
|
|
|
57224
57268
|
}
|
|
57225
57269
|
) });
|
|
57226
57270
|
}
|
|
57271
|
+
function computeScrollIdx(tasks, focusedRow, contextMenuRow, maxTasksHeight) {
|
|
57272
|
+
if (focusedRow < 0) return 0;
|
|
57273
|
+
const reserveBottom = focusedRow < tasks.length - 1 ? 1 : 0;
|
|
57274
|
+
let currentScroll = 0;
|
|
57275
|
+
while (currentScroll < focusedRow) {
|
|
57276
|
+
const hasTopIndicator = currentScroll > 0;
|
|
57277
|
+
const adjustedMaxHeight = maxTasksHeight - (hasTopIndicator ? 1 : 0) - reserveBottom;
|
|
57278
|
+
let h = 0;
|
|
57279
|
+
for (let k = currentScroll; k <= focusedRow; k++) {
|
|
57280
|
+
h += getTitleCategory(tasks[k].title) !== null ? 3 : 1;
|
|
57281
|
+
if (contextMenuRow === k) h += MENU_HEIGHT;
|
|
57282
|
+
if (k < focusedRow) h += 1;
|
|
57283
|
+
}
|
|
57284
|
+
if (h <= adjustedMaxHeight) break;
|
|
57285
|
+
currentScroll++;
|
|
57286
|
+
}
|
|
57287
|
+
return currentScroll;
|
|
57288
|
+
}
|
|
57227
57289
|
function KanbanColumn({
|
|
57228
57290
|
name,
|
|
57229
57291
|
tasks,
|
|
@@ -57234,14 +57296,48 @@ function KanbanColumn({
|
|
|
57234
57296
|
contextMenuCursor,
|
|
57235
57297
|
showMoveTarget,
|
|
57236
57298
|
isMoveFocused,
|
|
57237
|
-
draggedTaskId
|
|
57299
|
+
draggedTaskId,
|
|
57300
|
+
maxTasksHeight
|
|
57238
57301
|
}) {
|
|
57239
57302
|
const accent = columnAccentColor(name);
|
|
57240
57303
|
const headerBg = isFocused ? accent : void 0;
|
|
57241
57304
|
const headerColor = isFocused ? "black" : accent;
|
|
57242
57305
|
const countStr = tasks.length > 0 ? ` (${tasks.length})` : "";
|
|
57306
|
+
const scrollIdx = isFocused ? computeScrollIdx(tasks, focusedRow, contextMenuRow ?? -1, maxTasksHeight) : 0;
|
|
57307
|
+
let accumulatedHeight = 0;
|
|
57308
|
+
let endIdx = scrollIdx;
|
|
57309
|
+
const hasTopIndicator = scrollIdx > 0;
|
|
57310
|
+
const topIndicatorHeight = hasTopIndicator ? 1 : 0;
|
|
57311
|
+
while (endIdx < tasks.length) {
|
|
57312
|
+
const hasCategory = getTitleCategory(tasks[endIdx].title) !== null;
|
|
57313
|
+
let taskHeight = hasCategory ? 3 : 1;
|
|
57314
|
+
if (contextMenuRow === endIdx) taskHeight += MENU_HEIGHT;
|
|
57315
|
+
const sepHeight = endIdx < tasks.length - 1 ? 1 : 0;
|
|
57316
|
+
const hasBottomIndicator = endIdx < tasks.length - 1;
|
|
57317
|
+
const bottomIndicatorHeight = hasBottomIndicator ? 1 : 0;
|
|
57318
|
+
const currentMax = maxTasksHeight - topIndicatorHeight - bottomIndicatorHeight;
|
|
57319
|
+
if (accumulatedHeight + taskHeight + sepHeight > currentMax) {
|
|
57320
|
+
if (endIdx === scrollIdx) {
|
|
57321
|
+
endIdx++;
|
|
57322
|
+
}
|
|
57323
|
+
break;
|
|
57324
|
+
}
|
|
57325
|
+
accumulatedHeight += taskHeight + sepHeight;
|
|
57326
|
+
endIdx++;
|
|
57327
|
+
}
|
|
57243
57328
|
const rows = [];
|
|
57244
|
-
|
|
57329
|
+
if (hasTopIndicator) {
|
|
57330
|
+
rows.push(
|
|
57331
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "cyan", dimColor: true, children: [
|
|
57332
|
+
" ".repeat(2),
|
|
57333
|
+
"\u25B2 ",
|
|
57334
|
+
scrollIdx,
|
|
57335
|
+
" more"
|
|
57336
|
+
] }, "scroll-up")
|
|
57337
|
+
);
|
|
57338
|
+
}
|
|
57339
|
+
for (let idx = scrollIdx; idx < endIdx; idx++) {
|
|
57340
|
+
const task = tasks[idx];
|
|
57245
57341
|
const hasCategory = getTitleCategory(task.title) !== null;
|
|
57246
57342
|
rows.push(
|
|
57247
57343
|
hasCategory ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CategoryTaskRow, { task, focused: !!(isFocused && idx === focusedRow), dragging: task.id === draggedTaskId, colWidth }, task.id) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SingleTaskRow, { task, focused: !!(isFocused && idx === focusedRow), dragging: task.id === draggedTaskId, colWidth }, task.id)
|
|
@@ -57258,12 +57354,22 @@ function KanbanColumn({
|
|
|
57258
57354
|
)
|
|
57259
57355
|
);
|
|
57260
57356
|
}
|
|
57261
|
-
if (idx <
|
|
57357
|
+
if (idx < endIdx - 1) {
|
|
57262
57358
|
rows.push(
|
|
57263
57359
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: isFocused ? "cyan" : "gray", dimColor: true, children: "\u2500".repeat(colWidth) }, `sep-${task.id}`)
|
|
57264
57360
|
);
|
|
57265
57361
|
}
|
|
57266
|
-
}
|
|
57362
|
+
}
|
|
57363
|
+
if (endIdx < tasks.length) {
|
|
57364
|
+
rows.push(
|
|
57365
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Text, { color: "cyan", dimColor: true, children: [
|
|
57366
|
+
" ".repeat(2),
|
|
57367
|
+
"\u25BC ",
|
|
57368
|
+
tasks.length - endIdx,
|
|
57369
|
+
" more"
|
|
57370
|
+
] }, "scroll-down")
|
|
57371
|
+
);
|
|
57372
|
+
}
|
|
57267
57373
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", width: colWidth, marginRight: 1, children: [
|
|
57268
57374
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { backgroundColor: headerBg, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: headerColor, bold: true, children: pad(`${name}${countStr}`, colWidth) }) }),
|
|
57269
57375
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: isFocused ? "cyan" : "gray", children: "\u2500".repeat(colWidth) }),
|
|
@@ -57380,6 +57486,22 @@ function Board({ kandownDir, version }) {
|
|
|
57380
57486
|
const [rowIndex, setRowIndex] = (0, import_react37.useState)(0);
|
|
57381
57487
|
const [mode, setMode] = (0, import_react37.useState)("browse");
|
|
57382
57488
|
const [statusMsg, setStatusMsg] = (0, import_react37.useState)("");
|
|
57489
|
+
const statusTimerRef = (0, import_react37.useRef)(null);
|
|
57490
|
+
const showStatus = (0, import_react37.useCallback)((msg, ms = 2e3) => {
|
|
57491
|
+
setStatusMsg(msg);
|
|
57492
|
+
if (statusTimerRef.current) clearTimeout(statusTimerRef.current);
|
|
57493
|
+
if (msg) {
|
|
57494
|
+
statusTimerRef.current = setTimeout(() => {
|
|
57495
|
+
setStatusMsg("");
|
|
57496
|
+
statusTimerRef.current = null;
|
|
57497
|
+
}, ms);
|
|
57498
|
+
}
|
|
57499
|
+
}, []);
|
|
57500
|
+
(0, import_react37.useEffect)(() => {
|
|
57501
|
+
return () => {
|
|
57502
|
+
if (statusTimerRef.current) clearTimeout(statusTimerRef.current);
|
|
57503
|
+
};
|
|
57504
|
+
}, []);
|
|
57383
57505
|
const [boardError, setBoardError] = (0, import_react37.useState)(null);
|
|
57384
57506
|
const [detailTask, setDetailTask] = (0, import_react37.useState)(null);
|
|
57385
57507
|
const [detailTaskId, setDetailTaskId] = (0, import_react37.useState)("");
|
|
@@ -57395,45 +57517,73 @@ function Board({ kandownDir, version }) {
|
|
|
57395
57517
|
const [preferredDaemonPort, setPreferredDaemonPort] = (0, import_react37.useState)(null);
|
|
57396
57518
|
const [installedAgents, setInstalledAgents] = (0, import_react37.useState)([]);
|
|
57397
57519
|
const inTmux = isInTmux();
|
|
57398
|
-
const layoutRef = (0, import_react37.useRef)({
|
|
57399
|
-
colStarts: [],
|
|
57400
|
-
colWidth: 0
|
|
57401
|
-
});
|
|
57402
|
-
const updateLayout = (0, import_react37.useCallback)((b) => {
|
|
57403
|
-
if (!b) return;
|
|
57404
|
-
const cw = calcColWidth(b.columns.length);
|
|
57405
|
-
const starts = [];
|
|
57406
|
-
let x = 1;
|
|
57407
|
-
for (let i = 0; i < b.columns.length; i++) {
|
|
57408
|
-
starts.push(x);
|
|
57409
|
-
x += cw + 1;
|
|
57410
|
-
}
|
|
57411
|
-
layoutRef.current = { colStarts: starts, colWidth: cw };
|
|
57412
|
-
}, []);
|
|
57413
57520
|
const columnAtX = (0, import_react37.useCallback)((x) => {
|
|
57414
|
-
|
|
57415
|
-
|
|
57416
|
-
|
|
57417
|
-
|
|
57521
|
+
if (!board) return -1;
|
|
57522
|
+
const numCols = board.columns.length;
|
|
57523
|
+
const cw = calcColWidth(numCols);
|
|
57524
|
+
let startX = 1;
|
|
57525
|
+
for (let c = 0; c < numCols; c++) {
|
|
57526
|
+
if (x >= startX && x < startX + cw) return c;
|
|
57527
|
+
startX += cw + 1;
|
|
57418
57528
|
}
|
|
57419
57529
|
return -1;
|
|
57420
|
-
}, []);
|
|
57530
|
+
}, [board]);
|
|
57421
57531
|
const taskHitAt = (0, import_react37.useCallback)((x, y) => {
|
|
57422
57532
|
if (!board) return null;
|
|
57423
57533
|
const clickedCol = columnAtX(x);
|
|
57424
57534
|
if (clickedCol < 0) return null;
|
|
57425
57535
|
const col = board.columns[clickedCol];
|
|
57426
|
-
if (!col) return null;
|
|
57427
|
-
const
|
|
57428
|
-
const
|
|
57429
|
-
|
|
57430
|
-
|
|
57431
|
-
|
|
57536
|
+
if (!col || col.tasks.length === 0) return null;
|
|
57537
|
+
const maxTasksHeight = Math.max(5, (process.stdout.rows || 24) - TASKS_START_Y - 3 - (mode === "move-target" || mode === "dragging" ? 2 : 0));
|
|
57538
|
+
const isFocused = clickedCol === colIndex;
|
|
57539
|
+
const focusedRow = isFocused ? rowIndex : -1;
|
|
57540
|
+
const contextMenuRowVal = mode === "context-menu" && isFocused ? ctxMenuRow : -1;
|
|
57541
|
+
const scrollIdx = isFocused ? computeScrollIdx(col.tasks, focusedRow, contextMenuRowVal, maxTasksHeight) : 0;
|
|
57542
|
+
const hasTopIndicator = scrollIdx > 0;
|
|
57543
|
+
let currentY = TASKS_START_Y;
|
|
57544
|
+
if (hasTopIndicator) {
|
|
57545
|
+
if (y === currentY) return null;
|
|
57546
|
+
currentY += 1;
|
|
57547
|
+
}
|
|
57548
|
+
let endIdx = scrollIdx;
|
|
57549
|
+
let accumulatedHeight = 0;
|
|
57550
|
+
const topIndicatorHeight = hasTopIndicator ? 1 : 0;
|
|
57551
|
+
while (endIdx < col.tasks.length) {
|
|
57552
|
+
const hasCategory = getTitleCategory(col.tasks[endIdx].title) !== null;
|
|
57553
|
+
const taskHeight = hasCategory ? 3 : 1;
|
|
57554
|
+
const sepHeight = endIdx < col.tasks.length - 1 ? 1 : 0;
|
|
57555
|
+
const hasBottomIndicator = endIdx < col.tasks.length - 1;
|
|
57556
|
+
const bottomIndicatorHeight = hasBottomIndicator ? 1 : 0;
|
|
57557
|
+
const currentMax = maxTasksHeight - topIndicatorHeight - bottomIndicatorHeight;
|
|
57558
|
+
if (accumulatedHeight + taskHeight + sepHeight > currentMax) {
|
|
57559
|
+
if (endIdx === scrollIdx) {
|
|
57560
|
+
endIdx++;
|
|
57561
|
+
}
|
|
57562
|
+
break;
|
|
57563
|
+
}
|
|
57564
|
+
if (y >= currentY && y < currentY + taskHeight) {
|
|
57565
|
+
return { taskId: col.tasks[endIdx].id, colIndex: clickedCol, rowIndex: endIdx, startX: x, startY: y };
|
|
57566
|
+
}
|
|
57567
|
+
currentY += taskHeight;
|
|
57568
|
+
if (contextMenuRowVal === endIdx) {
|
|
57569
|
+
if (y >= currentY && y < currentY + MENU_HEIGHT) {
|
|
57570
|
+
return { taskId: col.tasks[endIdx].id, colIndex: clickedCol, rowIndex: endIdx, startX: x, startY: y, isMenu: true };
|
|
57571
|
+
}
|
|
57572
|
+
currentY += MENU_HEIGHT;
|
|
57573
|
+
}
|
|
57574
|
+
if (endIdx < col.tasks.length - 1) {
|
|
57575
|
+
if (y === currentY) return null;
|
|
57576
|
+
currentY += 1;
|
|
57577
|
+
}
|
|
57578
|
+
accumulatedHeight += taskHeight + sepHeight;
|
|
57579
|
+
endIdx++;
|
|
57580
|
+
}
|
|
57581
|
+
return null;
|
|
57582
|
+
}, [board, colIndex, rowIndex, mode, ctxMenuRow, columnAtX]);
|
|
57432
57583
|
const loadBoardInto = (0, import_react37.useCallback)(() => {
|
|
57433
57584
|
try {
|
|
57434
57585
|
const loaded = readBoard(kandownDir);
|
|
57435
57586
|
setBoard(loaded);
|
|
57436
|
-
updateLayout(loaded);
|
|
57437
57587
|
setBoardError(null);
|
|
57438
57588
|
return loaded;
|
|
57439
57589
|
} catch (e) {
|
|
@@ -57442,7 +57592,7 @@ function Board({ kandownDir, version }) {
|
|
|
57442
57592
|
setStatusMsg("");
|
|
57443
57593
|
return null;
|
|
57444
57594
|
}
|
|
57445
|
-
}, [kandownDir
|
|
57595
|
+
}, [kandownDir]);
|
|
57446
57596
|
(0, import_react37.useEffect)(() => {
|
|
57447
57597
|
loadBoardInto();
|
|
57448
57598
|
setInstalledAgents(detectInstalledAgents());
|
|
@@ -57454,8 +57604,7 @@ function Board({ kandownDir, version }) {
|
|
|
57454
57604
|
});
|
|
57455
57605
|
watcher.on("newTaskDetected", (taskId) => {
|
|
57456
57606
|
loadBoardInto();
|
|
57457
|
-
|
|
57458
|
-
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57607
|
+
showStatus(`New task: ${taskId}`, 2e3);
|
|
57459
57608
|
});
|
|
57460
57609
|
watcher.on("configChanged", () => {
|
|
57461
57610
|
loadBoardInto();
|
|
@@ -57464,14 +57613,13 @@ function Board({ kandownDir, version }) {
|
|
|
57464
57613
|
return () => {
|
|
57465
57614
|
watcher.stop();
|
|
57466
57615
|
};
|
|
57467
|
-
}, [kandownDir, loadBoardInto]);
|
|
57616
|
+
}, [kandownDir, loadBoardInto, showStatus]);
|
|
57468
57617
|
const reloadBoard = (0, import_react37.useCallback)(() => {
|
|
57469
57618
|
const loaded = loadBoardInto();
|
|
57470
57619
|
if (loaded) {
|
|
57471
|
-
|
|
57472
|
-
setTimeout(() => setStatusMsg(""), 1500);
|
|
57620
|
+
showStatus("Board reloaded", 1500);
|
|
57473
57621
|
}
|
|
57474
|
-
}, [loadBoardInto]);
|
|
57622
|
+
}, [loadBoardInto, showStatus]);
|
|
57475
57623
|
const refreshDaemonStatus = (0, import_react37.useCallback)(async () => {
|
|
57476
57624
|
const next = await getDaemonStatus(kandownDir);
|
|
57477
57625
|
setDaemonStatus(next);
|
|
@@ -57494,20 +57642,19 @@ function Board({ kandownDir, version }) {
|
|
|
57494
57642
|
await stopProjectDaemon(kandownDir);
|
|
57495
57643
|
const next = await getDaemonStatus(kandownDir);
|
|
57496
57644
|
setDaemonStatus(next);
|
|
57497
|
-
|
|
57645
|
+
showStatus("Web daemon stopped", 2500);
|
|
57498
57646
|
} else {
|
|
57499
57647
|
const next = await startProjectDaemon(kandownDir, preferredDaemonPort);
|
|
57500
57648
|
setDaemonStatus(next);
|
|
57501
57649
|
if (next.running && next.metadata) setPreferredDaemonPort(next.metadata.port);
|
|
57502
|
-
|
|
57650
|
+
showStatus(next.running ? "Web daemon started" : "Web daemon failed to start", 2500);
|
|
57503
57651
|
}
|
|
57504
57652
|
} catch (error) {
|
|
57505
|
-
|
|
57653
|
+
showStatus(`Daemon error: ${error instanceof Error ? error.message : String(error)}`, 2500);
|
|
57506
57654
|
} finally {
|
|
57507
57655
|
setDaemonBusy(false);
|
|
57508
|
-
setTimeout(() => setStatusMsg(""), 2500);
|
|
57509
57656
|
}
|
|
57510
|
-
}, [daemonBusy, kandownDir, preferredDaemonPort]);
|
|
57657
|
+
}, [daemonBusy, kandownDir, preferredDaemonPort, showStatus]);
|
|
57511
57658
|
const tryMoveWithGate = (0, import_react37.useCallback)((taskId, targetCol) => {
|
|
57512
57659
|
if (!board) return false;
|
|
57513
57660
|
const cfg = loadConfig(kandownDir);
|
|
@@ -57533,36 +57680,35 @@ function Board({ kandownDir, version }) {
|
|
|
57533
57680
|
}
|
|
57534
57681
|
if (blocked.length > 0) {
|
|
57535
57682
|
const list = blocked.length === 1 ? blocked[0] : `${blocked.slice(0, -1).join(", ")} and ${blocked[blocked.length - 1]}`;
|
|
57536
|
-
|
|
57537
|
-
setTimeout(() => setStatusMsg(""), 3500);
|
|
57683
|
+
showStatus(`Blocked: ${taskId} \u2190 ${list}`, 3500);
|
|
57538
57684
|
return false;
|
|
57539
57685
|
}
|
|
57540
57686
|
return true;
|
|
57541
|
-
}, [board, kandownDir]);
|
|
57687
|
+
}, [board, kandownDir, showStatus]);
|
|
57542
57688
|
const sendTaskToAgentHook = (0, import_react37.useCallback)(async (taskId) => {
|
|
57543
57689
|
const status = await getDaemonStatus(kandownDir);
|
|
57544
57690
|
if (!status.running || !status.metadata) {
|
|
57545
|
-
|
|
57546
|
-
setTimeout(() => setStatusMsg(""), 2500);
|
|
57691
|
+
showStatus("Web daemon not running (press d to start)", 2500);
|
|
57547
57692
|
return;
|
|
57548
57693
|
}
|
|
57549
57694
|
try {
|
|
57550
57695
|
const res = await fetch(`http://127.0.0.1:${status.metadata.port}/api/tasks/${encodeURIComponent(taskId)}/agent`, {
|
|
57551
57696
|
method: "POST",
|
|
57697
|
+
// 📖 The daemon requires its per-instance token (M5); the TUI reads it
|
|
57698
|
+
// from daemon.json via getDaemonStatus.
|
|
57699
|
+
headers: status.metadata.token ? { "X-Kandown-Token": status.metadata.token } : {},
|
|
57552
57700
|
signal: AbortSignal.timeout(8e3)
|
|
57553
57701
|
});
|
|
57554
57702
|
if (res.ok) {
|
|
57555
|
-
|
|
57703
|
+
showStatus(`Sent ${taskId} to agent hook`, 2e3);
|
|
57556
57704
|
} else {
|
|
57557
57705
|
const body = await res.text().catch(() => "");
|
|
57558
|
-
|
|
57706
|
+
showStatus(`Agent hook: ${res.status}${body ? " \u2014 " + body.slice(0, 60) : ""}`, 3e3);
|
|
57559
57707
|
}
|
|
57560
57708
|
} catch (error) {
|
|
57561
|
-
|
|
57562
|
-
} finally {
|
|
57563
|
-
setTimeout(() => setStatusMsg(""), 3e3);
|
|
57709
|
+
showStatus(`Agent hook failed: ${error instanceof Error ? error.message : String(error)}`, 3e3);
|
|
57564
57710
|
}
|
|
57565
|
-
}, [kandownDir]);
|
|
57711
|
+
}, [kandownDir, showStatus]);
|
|
57566
57712
|
const getFocusedTask = (0, import_react37.useCallback)(() => {
|
|
57567
57713
|
if (!board) return null;
|
|
57568
57714
|
const col = board.columns[colIndex];
|
|
@@ -57577,10 +57723,9 @@ function Board({ kandownDir, version }) {
|
|
|
57577
57723
|
setDetailScroll(0);
|
|
57578
57724
|
setMode("detail");
|
|
57579
57725
|
} catch (e) {
|
|
57580
|
-
|
|
57581
|
-
setTimeout(() => setStatusMsg(""), 4e3);
|
|
57726
|
+
showStatus(`Error opening task: ${e instanceof Error ? e.message : String(e)}`, 4e3);
|
|
57582
57727
|
}
|
|
57583
|
-
}, [kandownDir]);
|
|
57728
|
+
}, [kandownDir, showStatus]);
|
|
57584
57729
|
const closeContextMenu = (0, import_react37.useCallback)(() => {
|
|
57585
57730
|
setCtxMenuRow(-1);
|
|
57586
57731
|
setCtxMenuCursor(0);
|
|
@@ -57590,143 +57735,176 @@ function Board({ kandownDir, version }) {
|
|
|
57590
57735
|
const taskId = mode === "detail" ? detailTaskId : task?.id;
|
|
57591
57736
|
if (!taskId) return;
|
|
57592
57737
|
setMode("browse");
|
|
57593
|
-
|
|
57738
|
+
showStatus(`Launching ${agentId} for ${taskId}\u2026`, 5e3);
|
|
57594
57739
|
setTimeout(() => {
|
|
57595
57740
|
try {
|
|
57596
57741
|
launchAgent({ taskId, agentId, kandownDir, onBeforeExec: () => exit() });
|
|
57597
57742
|
reloadBoard();
|
|
57598
|
-
|
|
57599
|
-
setTimeout(() => setStatusMsg(""), 3e3);
|
|
57743
|
+
showStatus(`${agentId} launched in tmux pane`, 3e3);
|
|
57600
57744
|
} catch (err) {
|
|
57601
|
-
|
|
57602
|
-
setTimeout(() => setStatusMsg(""), 4e3);
|
|
57745
|
+
showStatus(`Error: ${err instanceof Error ? err.message : String(err)}`, 4e3);
|
|
57603
57746
|
}
|
|
57604
57747
|
}, 50);
|
|
57605
|
-
}, [mode, detailTaskId, getFocusedTask, kandownDir, exit, reloadBoard]);
|
|
57748
|
+
}, [mode, detailTaskId, getFocusedTask, kandownDir, exit, reloadBoard, showStatus]);
|
|
57606
57749
|
useMouseMode(mode !== "agent-picker");
|
|
57607
57750
|
const handleMouseClick = (0, import_react37.useCallback)((x, y) => {
|
|
57608
57751
|
if (!board) return;
|
|
57609
|
-
const
|
|
57610
|
-
|
|
57611
|
-
|
|
57612
|
-
|
|
57613
|
-
|
|
57614
|
-
|
|
57752
|
+
const clickedCol = columnAtX(x);
|
|
57753
|
+
if (clickedCol < 0) {
|
|
57754
|
+
if (mode === "context-menu") {
|
|
57755
|
+
closeContextMenu();
|
|
57756
|
+
setMode("browse");
|
|
57757
|
+
} else if (mode === "move-target") {
|
|
57758
|
+
setMoveTaskId(null);
|
|
57759
|
+
setMode("browse");
|
|
57760
|
+
}
|
|
57761
|
+
return;
|
|
57762
|
+
}
|
|
57763
|
+
const col = board.columns[clickedCol];
|
|
57764
|
+
if (mode === "move-target") {
|
|
57765
|
+
if (clickedCol === colIndex) {
|
|
57766
|
+
setMoveTaskId(null);
|
|
57767
|
+
setMode("browse");
|
|
57768
|
+
return;
|
|
57769
|
+
}
|
|
57770
|
+
if (moveTaskId) {
|
|
57771
|
+
if (!tryMoveWithGate(moveTaskId, col.name)) {
|
|
57772
|
+
setMoveTaskId(null);
|
|
57773
|
+
setMode("browse");
|
|
57774
|
+
return;
|
|
57775
|
+
}
|
|
57776
|
+
moveTaskToColumn(kandownDir, moveTaskId, col.name);
|
|
57777
|
+
loadBoardInto();
|
|
57778
|
+
showStatus(`Moved ${moveTaskId} \u2192 ${col.name}`);
|
|
57779
|
+
}
|
|
57780
|
+
setMoveTaskId(null);
|
|
57781
|
+
setMode("browse");
|
|
57782
|
+
return;
|
|
57783
|
+
}
|
|
57784
|
+
const maxTasksHeight = Math.max(5, (process.stdout.rows || 24) - TASKS_START_Y - 3 - (mode === "dragging" ? 2 : 0));
|
|
57785
|
+
const isFocused = clickedCol === colIndex;
|
|
57786
|
+
const focusedRow = isFocused ? rowIndex : -1;
|
|
57787
|
+
const contextMenuRowVal = mode === "context-menu" && isFocused ? ctxMenuRow : -1;
|
|
57788
|
+
const scrollIdx = isFocused ? computeScrollIdx(col.tasks, focusedRow, contextMenuRowVal, maxTasksHeight) : 0;
|
|
57789
|
+
const hasTopIndicator = scrollIdx > 0;
|
|
57790
|
+
let currentY = TASKS_START_Y;
|
|
57791
|
+
if (hasTopIndicator) {
|
|
57792
|
+
if (y === currentY) {
|
|
57793
|
+
if (isFocused) {
|
|
57794
|
+
setRowIndex((r) => Math.max(0, r - 1));
|
|
57795
|
+
}
|
|
57796
|
+
return;
|
|
57797
|
+
}
|
|
57798
|
+
currentY += 1;
|
|
57799
|
+
}
|
|
57800
|
+
let endIdx = scrollIdx;
|
|
57801
|
+
let accumulatedHeight = 0;
|
|
57802
|
+
const topIndicatorHeight = hasTopIndicator ? 1 : 0;
|
|
57803
|
+
let clickedTaskIdx = -1;
|
|
57804
|
+
let clickedMenuOffset = -1;
|
|
57805
|
+
while (endIdx < col.tasks.length) {
|
|
57806
|
+
const hasCategory = getTitleCategory(col.tasks[endIdx].title) !== null;
|
|
57807
|
+
const taskHeight = hasCategory ? 3 : 1;
|
|
57808
|
+
const sepHeight = endIdx < col.tasks.length - 1 ? 1 : 0;
|
|
57809
|
+
const hasBottomIndicator = endIdx < col.tasks.length - 1;
|
|
57810
|
+
const bottomIndicatorHeight = hasBottomIndicator ? 1 : 0;
|
|
57811
|
+
const currentMax = maxTasksHeight - topIndicatorHeight - bottomIndicatorHeight;
|
|
57812
|
+
if (accumulatedHeight + taskHeight + sepHeight > currentMax) {
|
|
57813
|
+
if (endIdx === scrollIdx) {
|
|
57814
|
+
endIdx++;
|
|
57815
|
+
}
|
|
57615
57816
|
break;
|
|
57616
57817
|
}
|
|
57818
|
+
if (y >= currentY && y < currentY + taskHeight) {
|
|
57819
|
+
clickedTaskIdx = endIdx;
|
|
57820
|
+
break;
|
|
57821
|
+
}
|
|
57822
|
+
currentY += taskHeight;
|
|
57823
|
+
if (contextMenuRowVal === endIdx) {
|
|
57824
|
+
if (y >= currentY && y < currentY + MENU_HEIGHT) {
|
|
57825
|
+
clickedTaskIdx = endIdx;
|
|
57826
|
+
clickedMenuOffset = y - currentY;
|
|
57827
|
+
break;
|
|
57828
|
+
}
|
|
57829
|
+
currentY += MENU_HEIGHT;
|
|
57830
|
+
}
|
|
57831
|
+
if (endIdx < col.tasks.length - 1) {
|
|
57832
|
+
if (y === currentY) {
|
|
57833
|
+
return;
|
|
57834
|
+
}
|
|
57835
|
+
currentY += 1;
|
|
57836
|
+
}
|
|
57837
|
+
accumulatedHeight += taskHeight + sepHeight;
|
|
57838
|
+
endIdx++;
|
|
57839
|
+
}
|
|
57840
|
+
if (endIdx < col.tasks.length) {
|
|
57841
|
+
if (y === currentY) {
|
|
57842
|
+
if (isFocused) {
|
|
57843
|
+
setRowIndex((r) => Math.min(col.tasks.length - 1, r + 1));
|
|
57844
|
+
}
|
|
57845
|
+
return;
|
|
57846
|
+
}
|
|
57847
|
+
currentY += 1;
|
|
57617
57848
|
}
|
|
57618
57849
|
if (mode === "browse") {
|
|
57619
|
-
if (
|
|
57620
|
-
const col = board.columns[clickedCol];
|
|
57621
|
-
const taskIdx = y - TASKS_START_Y;
|
|
57622
|
-
if (taskIdx >= 0 && taskIdx < col.tasks.length) {
|
|
57850
|
+
if (clickedTaskIdx >= 0) {
|
|
57623
57851
|
setColIndex(clickedCol);
|
|
57624
|
-
setRowIndex(
|
|
57625
|
-
setCtxMenuRow(
|
|
57852
|
+
setRowIndex(clickedTaskIdx);
|
|
57853
|
+
setCtxMenuRow(clickedTaskIdx);
|
|
57626
57854
|
setCtxMenuCursor(0);
|
|
57627
57855
|
setMode("context-menu");
|
|
57628
57856
|
}
|
|
57629
57857
|
return;
|
|
57630
57858
|
}
|
|
57631
57859
|
if (mode === "context-menu") {
|
|
57632
|
-
if (clickedCol < 0) {
|
|
57633
|
-
closeContextMenu();
|
|
57634
|
-
setMode("browse");
|
|
57635
|
-
return;
|
|
57636
|
-
}
|
|
57637
|
-
const col = board.columns[clickedCol];
|
|
57638
57860
|
const hasMenu = clickedCol === colIndex && ctxMenuRow >= 0;
|
|
57639
57861
|
if (hasMenu) {
|
|
57640
|
-
|
|
57641
|
-
|
|
57642
|
-
|
|
57643
|
-
|
|
57644
|
-
|
|
57645
|
-
|
|
57646
|
-
|
|
57647
|
-
|
|
57648
|
-
|
|
57649
|
-
|
|
57650
|
-
|
|
57651
|
-
|
|
57652
|
-
|
|
57653
|
-
|
|
57654
|
-
|
|
57655
|
-
|
|
57656
|
-
|
|
57657
|
-
closeContextMenu();
|
|
57658
|
-
openDetail(task.id);
|
|
57862
|
+
if (clickedTaskIdx >= 0) {
|
|
57863
|
+
if (clickedMenuOffset >= 0) {
|
|
57864
|
+
if (clickedMenuOffset === 0) {
|
|
57865
|
+
const task = col.tasks[ctxMenuRow];
|
|
57866
|
+
if (task) {
|
|
57867
|
+
closeContextMenu();
|
|
57868
|
+
openDetail(task.id);
|
|
57869
|
+
}
|
|
57870
|
+
} else {
|
|
57871
|
+
const task = col.tasks[ctxMenuRow];
|
|
57872
|
+
if (task) {
|
|
57873
|
+
setMoveTaskId(task.id);
|
|
57874
|
+
const target = colIndex === 0 ? Math.min(1, board.columns.length - 1) : 0;
|
|
57875
|
+
setMoveTargetCol(target);
|
|
57876
|
+
closeContextMenu();
|
|
57877
|
+
setMode("move-target");
|
|
57878
|
+
}
|
|
57659
57879
|
}
|
|
57880
|
+
} else if (clickedTaskIdx === ctxMenuRow) {
|
|
57881
|
+
closeContextMenu();
|
|
57882
|
+
setMode("browse");
|
|
57660
57883
|
} else {
|
|
57661
|
-
|
|
57662
|
-
|
|
57663
|
-
|
|
57664
|
-
|
|
57665
|
-
setMoveTargetCol(target);
|
|
57666
|
-
closeContextMenu();
|
|
57667
|
-
setMode("move-target");
|
|
57668
|
-
}
|
|
57884
|
+
setRowIndex(clickedTaskIdx);
|
|
57885
|
+
closeContextMenu();
|
|
57886
|
+
setCtxMenuRow(clickedTaskIdx);
|
|
57887
|
+
setCtxMenuCursor(0);
|
|
57669
57888
|
}
|
|
57670
|
-
|
|
57671
|
-
}
|
|
57672
|
-
const belowIdx = taskIdx - MENU_HEIGHT;
|
|
57673
|
-
if (belowIdx >= 0 && belowIdx < col.tasks.length) {
|
|
57674
|
-
setRowIndex(belowIdx);
|
|
57889
|
+
} else {
|
|
57675
57890
|
closeContextMenu();
|
|
57676
|
-
|
|
57677
|
-
setCtxMenuCursor(0);
|
|
57678
|
-
return;
|
|
57891
|
+
setMode("browse");
|
|
57679
57892
|
}
|
|
57680
57893
|
} else {
|
|
57681
|
-
|
|
57682
|
-
if (taskIdx >= 0 && taskIdx < col.tasks.length) {
|
|
57894
|
+
if (clickedTaskIdx >= 0) {
|
|
57683
57895
|
closeContextMenu();
|
|
57684
57896
|
setColIndex(clickedCol);
|
|
57685
|
-
setRowIndex(
|
|
57686
|
-
setCtxMenuRow(
|
|
57897
|
+
setRowIndex(clickedTaskIdx);
|
|
57898
|
+
setCtxMenuRow(clickedTaskIdx);
|
|
57687
57899
|
setCtxMenuCursor(0);
|
|
57688
|
-
|
|
57689
|
-
|
|
57690
|
-
|
|
57691
|
-
closeContextMenu();
|
|
57692
|
-
setMode("browse");
|
|
57693
|
-
return;
|
|
57694
|
-
}
|
|
57695
|
-
if (mode === "move-target") {
|
|
57696
|
-
if (clickedCol < 0) {
|
|
57697
|
-
setMoveTaskId(null);
|
|
57698
|
-
setMode("browse");
|
|
57699
|
-
return;
|
|
57700
|
-
}
|
|
57701
|
-
if (clickedCol === colIndex) {
|
|
57702
|
-
setMoveTaskId(null);
|
|
57703
|
-
setMode("browse");
|
|
57704
|
-
return;
|
|
57705
|
-
}
|
|
57706
|
-
const col = board.columns[clickedCol];
|
|
57707
|
-
const placeholderY = TASKS_START_Y + col.tasks.length;
|
|
57708
|
-
if (y === placeholderY) {
|
|
57709
|
-
const targetColName = col.name;
|
|
57710
|
-
if (moveTaskId) {
|
|
57711
|
-
if (!tryMoveWithGate(moveTaskId, targetColName)) {
|
|
57712
|
-
setMoveTaskId(null);
|
|
57713
|
-
setMode("browse");
|
|
57714
|
-
return;
|
|
57715
|
-
}
|
|
57716
|
-
moveTaskToColumn(kandownDir, moveTaskId, targetColName);
|
|
57717
|
-
loadBoardInto();
|
|
57718
|
-
setStatusMsg(`Moved ${moveTaskId} \u2192 ${targetColName}`);
|
|
57719
|
-
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57900
|
+
} else {
|
|
57901
|
+
closeContextMenu();
|
|
57902
|
+
setMode("browse");
|
|
57720
57903
|
}
|
|
57721
|
-
setMoveTaskId(null);
|
|
57722
|
-
setMode("browse");
|
|
57723
|
-
return;
|
|
57724
57904
|
}
|
|
57725
|
-
setMoveTaskId(null);
|
|
57726
|
-
setMode("browse");
|
|
57727
57905
|
return;
|
|
57728
57906
|
}
|
|
57729
|
-
}, [board, mode, colIndex, rowIndex, ctxMenuRow, moveTaskId, kandownDir,
|
|
57907
|
+
}, [board, mode, colIndex, rowIndex, ctxMenuRow, moveTaskId, kandownDir, columnAtX, closeContextMenu, openDetail, loadBoardInto, tryMoveWithGate, taskDrag, showStatus]);
|
|
57730
57908
|
const handleMouseEvent = (0, import_react37.useCallback)((mouse) => {
|
|
57731
57909
|
if (!board) return;
|
|
57732
57910
|
if (mode === "browse") {
|
|
@@ -57792,8 +57970,7 @@ function Board({ kandownDir, version }) {
|
|
|
57792
57970
|
setColIndex(targetCol);
|
|
57793
57971
|
const movedRow = loaded?.columns[targetCol]?.tasks.findIndex((task) => task.id === taskDrag.taskId) ?? 0;
|
|
57794
57972
|
setRowIndex(Math.max(0, movedRow));
|
|
57795
|
-
|
|
57796
|
-
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57973
|
+
showStatus(`Dragged ${taskDrag.taskId} \u2192 ${targetColName}`, 2e3);
|
|
57797
57974
|
}
|
|
57798
57975
|
}
|
|
57799
57976
|
setTaskDrag(null);
|
|
@@ -57807,7 +57984,7 @@ function Board({ kandownDir, version }) {
|
|
|
57807
57984
|
if (mouse.action === "press" && mouse.button === 0) {
|
|
57808
57985
|
handleMouseClick(mouse.x, mouse.y);
|
|
57809
57986
|
}
|
|
57810
|
-
}, [board, mode, mousePress, taskDrag, taskHitAt, columnAtX, closeContextMenu, handleMouseClick, kandownDir
|
|
57987
|
+
}, [board, mode, mousePress, taskDrag, taskHitAt, columnAtX, closeContextMenu, handleMouseClick, kandownDir]);
|
|
57811
57988
|
use_input_default((input, key) => {
|
|
57812
57989
|
if (isMouseInput(input)) {
|
|
57813
57990
|
const mouse = parseMouseInput(input);
|
|
@@ -57864,8 +58041,7 @@ function Board({ kandownDir, version }) {
|
|
|
57864
58041
|
}
|
|
57865
58042
|
if (input === "a") {
|
|
57866
58043
|
if (installedAgents.length === 0) {
|
|
57867
|
-
|
|
57868
|
-
setTimeout(() => setStatusMsg(""), 3e3);
|
|
58044
|
+
showStatus("No AI agents found in PATH", 3e3);
|
|
57869
58045
|
return;
|
|
57870
58046
|
}
|
|
57871
58047
|
const task = getFocusedTask();
|
|
@@ -57953,8 +58129,7 @@ function Board({ kandownDir, version }) {
|
|
|
57953
58129
|
}
|
|
57954
58130
|
moveTaskToColumn(kandownDir, moveTaskId, name);
|
|
57955
58131
|
loadBoardInto();
|
|
57956
|
-
|
|
57957
|
-
setTimeout(() => setStatusMsg(""), 2e3);
|
|
58132
|
+
showStatus(`Moved ${moveTaskId} \u2192 ${name}`, 2e3);
|
|
57958
58133
|
}
|
|
57959
58134
|
setMoveTaskId(null);
|
|
57960
58135
|
setMode("browse");
|
|
@@ -57967,7 +58142,10 @@ function Board({ kandownDir, version }) {
|
|
|
57967
58142
|
return;
|
|
57968
58143
|
}
|
|
57969
58144
|
if (input === "j" || key.downArrow) {
|
|
57970
|
-
|
|
58145
|
+
const bodyLines = detailTask ? detailTask.body.split("\n") : [];
|
|
58146
|
+
const maxVisible = (process.stdout.rows || 24) - 10;
|
|
58147
|
+
const maxScroll = Math.max(0, bodyLines.length - maxVisible);
|
|
58148
|
+
setDetailScroll((s) => Math.min(s + 1, maxScroll));
|
|
57971
58149
|
return;
|
|
57972
58150
|
}
|
|
57973
58151
|
if (input === "k" || key.upArrow) {
|
|
@@ -57976,8 +58154,7 @@ function Board({ kandownDir, version }) {
|
|
|
57976
58154
|
}
|
|
57977
58155
|
if (input === "a") {
|
|
57978
58156
|
if (installedAgents.length === 0) {
|
|
57979
|
-
|
|
57980
|
-
setTimeout(() => setStatusMsg(""), 3e3);
|
|
58157
|
+
showStatus("No AI agents found in PATH", 3e3);
|
|
57981
58158
|
return;
|
|
57982
58159
|
}
|
|
57983
58160
|
setMode("agent-picker");
|
|
@@ -58014,7 +58191,7 @@ function Board({ kandownDir, version }) {
|
|
|
58014
58191
|
if (mode === "context-menu") {
|
|
58015
58192
|
modeHint = "j/k choose \xB7 Enter confirm \xB7 Esc cancel \xB7 or click";
|
|
58016
58193
|
} else if (mode === "move-target") {
|
|
58017
|
-
modeHint = "\u2190/\u2192 pick column \xB7 Enter confirm \xB7 Esc cancel \xB7 or click
|
|
58194
|
+
modeHint = "\u2190/\u2192 pick column \xB7 Enter confirm \xB7 Esc cancel \xB7 or click a column";
|
|
58018
58195
|
} else if (mode === "dragging") {
|
|
58019
58196
|
modeHint = "drag over target column \xB7 release to drop \xB7 Esc cancel";
|
|
58020
58197
|
}
|
|
@@ -58060,7 +58237,8 @@ function Board({ kandownDir, version }) {
|
|
|
58060
58237
|
contextMenuCursor: ctxMenuCursor,
|
|
58061
58238
|
showMoveTarget: mode === "move-target" && cIdx !== colIndex || mode === "dragging" && cIdx !== taskDrag?.sourceCol,
|
|
58062
58239
|
isMoveFocused: (mode === "move-target" || mode === "dragging") && cIdx === moveTargetCol,
|
|
58063
|
-
draggedTaskId: taskDrag?.taskId ?? null
|
|
58240
|
+
draggedTaskId: taskDrag?.taskId ?? null,
|
|
58241
|
+
maxTasksHeight: Math.max(5, (process.stdout.rows || 24) - TASKS_START_Y - 3 - (mode === "move-target" || mode === "dragging" ? 2 : 0))
|
|
58064
58242
|
},
|
|
58065
58243
|
col.name
|
|
58066
58244
|
)) }),
|
|
@@ -58068,7 +58246,7 @@ function Board({ kandownDir, version }) {
|
|
|
58068
58246
|
"Moving ",
|
|
58069
58247
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "cyan", children: moveTaskId }),
|
|
58070
58248
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: " \u2014 " }),
|
|
58071
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", bold: true, children: mode === "dragging" ? "release over a column" : "click
|
|
58249
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "yellow", bold: true, children: mode === "dragging" ? "release over a column" : "click a column" }),
|
|
58072
58250
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: mode === "dragging" ? " \xB7 Esc cancel" : " or \u2190/\u2192 + Enter" })
|
|
58073
58251
|
] }) }),
|
|
58074
58252
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StatusBar, { message: statusMsg, task: focusedTask, daemonStatus })
|