kandown 0.14.0 → 0.15.0
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/bin/kandown.js +64 -14
- package/bin/tui.js +174 -92
- package/dist/index.html +146 -143
- package/package.json +1 -1
package/bin/kandown.js
CHANGED
|
@@ -322,18 +322,43 @@ ${c.bold}Examples:${c.reset}
|
|
|
322
322
|
`);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
325
|
+
/**
|
|
326
|
+
* 📖 Resilient recursive copy used by `kandown init` (t113).
|
|
327
|
+
* Returns the list of per-entry error messages; an empty array means a fully
|
|
328
|
+
* clean copy. A partially-failed init no longer leaves the user with no clue
|
|
329
|
+
* about what went wrong — the caller reports each failure.
|
|
330
|
+
*/
|
|
331
|
+
function copyRecursive(src, dest, errors = []) {
|
|
332
|
+
if (!existsSync(src)) return errors;
|
|
333
|
+
try {
|
|
334
|
+
mkdirSync(dest, { recursive: true });
|
|
335
|
+
} catch (e) {
|
|
336
|
+
errors.push(`Failed to create directory ${dest}: ${e.message}`);
|
|
337
|
+
return errors;
|
|
338
|
+
}
|
|
339
|
+
let entries;
|
|
340
|
+
try {
|
|
341
|
+
entries = readdirSync(src);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
errors.push(`Failed to read ${src}: ${e.message}`);
|
|
344
|
+
return errors;
|
|
345
|
+
}
|
|
346
|
+
for (const entry of entries) {
|
|
329
347
|
const srcPath = join(src, entry);
|
|
330
348
|
const destPath = join(dest, entry);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
349
|
+
try {
|
|
350
|
+
if (statSync(srcPath).isDirectory()) {
|
|
351
|
+
copyRecursive(srcPath, destPath, errors);
|
|
352
|
+
} else {
|
|
353
|
+
copyFileSync(srcPath, destPath);
|
|
354
|
+
}
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// 📖 One unreadable / unwritable file shouldn't abort the whole init —
|
|
357
|
+
// record the error and keep copying the rest (t113).
|
|
358
|
+
errors.push(`Failed to copy ${srcPath}: ${e.message}`);
|
|
335
359
|
}
|
|
336
360
|
}
|
|
361
|
+
return errors;
|
|
337
362
|
}
|
|
338
363
|
|
|
339
364
|
function findAgentsFile(cwd) {
|
|
@@ -348,7 +373,15 @@ function findAgentsFile(cwd) {
|
|
|
348
373
|
function appendAgentReference(cwd, agentsFile, kandownPath) {
|
|
349
374
|
const filePath = join(cwd, agentsFile);
|
|
350
375
|
const marker = '<!-- kandown:agent-ref -->';
|
|
351
|
-
|
|
376
|
+
let existing;
|
|
377
|
+
try {
|
|
378
|
+
existing = readFileSync(filePath, 'utf8');
|
|
379
|
+
} catch (e) {
|
|
380
|
+
// 📖 TOCTOU: file existed at the check but vanished or became unreadable.
|
|
381
|
+
// Warn and skip rather than crashing init (t113).
|
|
382
|
+
warn(`Could not read ${agentsFile} (${e.message}); skipping agent reference.`);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
352
385
|
|
|
353
386
|
if (existing.includes(marker)) {
|
|
354
387
|
info(`${agentsFile} already references the kandown (skipped)`);
|
|
@@ -368,8 +401,13 @@ This project uses a file-based kanban:
|
|
|
368
401
|
- **Completion workflow:** set task frontmatter \`status: Done\` + write the completion report
|
|
369
402
|
`;
|
|
370
403
|
|
|
371
|
-
|
|
372
|
-
|
|
404
|
+
try {
|
|
405
|
+
writeFileSync(filePath, existing + ref, 'utf8');
|
|
406
|
+
return true;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
warn(`Could not append agent reference to ${agentsFile} (${e.message}).`);
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
373
411
|
}
|
|
374
412
|
|
|
375
413
|
function createAgentsFileIfMissing(cwd, kandownPath) {
|
|
@@ -586,7 +624,13 @@ function doInit(args, cwd, kandownPath, kandownDir) {
|
|
|
586
624
|
const tasksDest = getTasksDir(kandownDir);
|
|
587
625
|
if (!existsSync(tasksDest)) {
|
|
588
626
|
mkdirSync(tasksDest, { recursive: true });
|
|
589
|
-
copyRecursive
|
|
627
|
+
// 📖 copyRecursive now returns an errors[] array — report any partial
|
|
628
|
+
// failures instead of letting them crash the whole init (t113).
|
|
629
|
+
const copyErrors = copyRecursive(tasksSrc, tasksDest);
|
|
630
|
+
if (copyErrors.length > 0) {
|
|
631
|
+
warn(`Some task template files could not be copied:`);
|
|
632
|
+
for (const msg of copyErrors) warn(` - ${msg}`);
|
|
633
|
+
}
|
|
590
634
|
success('./tasks/ (with welcome example)');
|
|
591
635
|
} else {
|
|
592
636
|
info('./tasks/ already exists (kept)');
|
|
@@ -1747,7 +1791,10 @@ function serveApp(res, kandownDir) {
|
|
|
1747
1791
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
1748
1792
|
res.end(injected);
|
|
1749
1793
|
} catch (e) {
|
|
1750
|
-
|
|
1794
|
+
// 📖 Don't leak internal paths / error details to HTTP clients — log the
|
|
1795
|
+
// full error server-side, send a generic message to the browser (t113).
|
|
1796
|
+
console.error(`[serve] Error serving ${htmlPath}:`, e);
|
|
1797
|
+
writeText(res, 500, 'Internal server error — check the terminal where kandown is running.');
|
|
1751
1798
|
}
|
|
1752
1799
|
}
|
|
1753
1800
|
|
|
@@ -1837,7 +1884,10 @@ async function listenOnAvailablePort(kandownDir, preferredPort) {
|
|
|
1837
1884
|
await listen(server, p);
|
|
1838
1885
|
return { server, port: p };
|
|
1839
1886
|
} catch (e) {
|
|
1840
|
-
|
|
1887
|
+
// 📖 EADDRINUSE → try the next port. EACCES (privileged port / blocked
|
|
1888
|
+
// by OS) → also try the next port instead of crashing (t113). Anything
|
|
1889
|
+
// else is unexpected and bubbles up.
|
|
1890
|
+
if (e.code !== 'EADDRINUSE' && e.code !== 'EACCES') throw e;
|
|
1841
1891
|
}
|
|
1842
1892
|
}
|
|
1843
1893
|
|
package/bin/tui.js
CHANGED
|
@@ -54017,24 +54017,37 @@ var DEFAULT_CONFIG = {
|
|
|
54017
54017
|
function loadConfig(kandownDir) {
|
|
54018
54018
|
const configPath = join(kandownDir, "kandown.json");
|
|
54019
54019
|
if (!existsSync2(configPath)) return structuredClone(DEFAULT_CONFIG);
|
|
54020
|
+
let raw;
|
|
54020
54021
|
try {
|
|
54021
|
-
|
|
54022
|
-
|
|
54023
|
-
|
|
54024
|
-
|
|
54025
|
-
|
|
54026
|
-
|
|
54027
|
-
|
|
54028
|
-
|
|
54029
|
-
|
|
54030
|
-
fields: { ...DEFAULT_CONFIG.fields, ...raw.fields },
|
|
54031
|
-
notifications: { ...DEFAULT_CONFIG.notifications, ...raw.notifications }
|
|
54032
|
-
};
|
|
54033
|
-
if (raw.agents) merged.agents = raw.agents;
|
|
54034
|
-
return merged;
|
|
54035
|
-
} catch {
|
|
54022
|
+
raw = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
54023
|
+
} catch (e) {
|
|
54024
|
+
const err = e;
|
|
54025
|
+
if (err.code === "ENOENT") return structuredClone(DEFAULT_CONFIG);
|
|
54026
|
+
console.warn(`[kandown] kandown.json is corrupted, using defaults: ${e.message}`);
|
|
54027
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
54028
|
+
}
|
|
54029
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
54030
|
+
console.warn("[kandown] kandown.json must be a JSON object, using defaults.");
|
|
54036
54031
|
return structuredClone(DEFAULT_CONFIG);
|
|
54037
54032
|
}
|
|
54033
|
+
const obj = raw;
|
|
54034
|
+
const safeObj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
54035
|
+
const boardRaw = safeObj(obj.board);
|
|
54036
|
+
const merged = {
|
|
54037
|
+
ui: { ...DEFAULT_CONFIG.ui, ...safeObj(obj.ui) },
|
|
54038
|
+
agent: { ...DEFAULT_CONFIG.agent, ...safeObj(obj.agent) },
|
|
54039
|
+
board: {
|
|
54040
|
+
...DEFAULT_CONFIG.board,
|
|
54041
|
+
...boardRaw,
|
|
54042
|
+
columns: Array.isArray(boardRaw.columns) && boardRaw.columns.length > 0 ? boardRaw.columns.filter((name) => typeof name === "string" && name.trim().length > 0) : DEFAULT_CONFIG.board.columns
|
|
54043
|
+
},
|
|
54044
|
+
fields: { ...DEFAULT_CONFIG.fields, ...safeObj(obj.fields) },
|
|
54045
|
+
notifications: { ...DEFAULT_CONFIG.notifications, ...safeObj(obj.notifications) }
|
|
54046
|
+
};
|
|
54047
|
+
if (obj.agents && typeof obj.agents === "object") {
|
|
54048
|
+
merged.agents = obj.agents;
|
|
54049
|
+
}
|
|
54050
|
+
return merged;
|
|
54038
54051
|
}
|
|
54039
54052
|
function saveConfig(kandownDir, config) {
|
|
54040
54053
|
const configPath = join(kandownDir, "kandown.json");
|
|
@@ -54172,8 +54185,12 @@ function Settings({ kandownDir, version }) {
|
|
|
54172
54185
|
const persistConfig = (0, import_react34.useCallback)(
|
|
54173
54186
|
(newConfig) => {
|
|
54174
54187
|
setConfig(newConfig);
|
|
54175
|
-
|
|
54176
|
-
|
|
54188
|
+
try {
|
|
54189
|
+
saveConfig(kandownDir, newConfig);
|
|
54190
|
+
setSavedAt(Date.now());
|
|
54191
|
+
} catch (e) {
|
|
54192
|
+
console.error("[kandown] Failed to save config:", e);
|
|
54193
|
+
}
|
|
54177
54194
|
},
|
|
54178
54195
|
[kandownDir]
|
|
54179
54196
|
);
|
|
@@ -54555,17 +54572,23 @@ function listTaskIds(kandownDir) {
|
|
|
54555
54572
|
}
|
|
54556
54573
|
function readBoard(kandownDir) {
|
|
54557
54574
|
const config = loadConfig(kandownDir);
|
|
54558
|
-
const
|
|
54559
|
-
|
|
54560
|
-
|
|
54561
|
-
|
|
54562
|
-
|
|
54563
|
-
|
|
54564
|
-
|
|
54565
|
-
|
|
54566
|
-
|
|
54567
|
-
|
|
54568
|
-
|
|
54575
|
+
const ids = listTaskIds(kandownDir);
|
|
54576
|
+
const tasks = [];
|
|
54577
|
+
for (const id of ids) {
|
|
54578
|
+
try {
|
|
54579
|
+
const task = readTask(kandownDir, id);
|
|
54580
|
+
tasks.push({
|
|
54581
|
+
...task,
|
|
54582
|
+
frontmatter: {
|
|
54583
|
+
...task.frontmatter,
|
|
54584
|
+
id: task.frontmatter.id || id,
|
|
54585
|
+
status: task.frontmatter.status || "Backlog"
|
|
54586
|
+
}
|
|
54587
|
+
});
|
|
54588
|
+
} catch (e) {
|
|
54589
|
+
console.error(`[kandown] Failed to read task ${id}:`, e.message);
|
|
54590
|
+
}
|
|
54591
|
+
}
|
|
54569
54592
|
return {
|
|
54570
54593
|
frontmatter: null,
|
|
54571
54594
|
title: "Project Kanban",
|
|
@@ -54598,8 +54621,11 @@ function readAgentDoc(kandownDir) {
|
|
|
54598
54621
|
join2(kandownDir, "AGENT.md")
|
|
54599
54622
|
];
|
|
54600
54623
|
for (const candidate of candidates) {
|
|
54601
|
-
if (existsSync3(candidate))
|
|
54624
|
+
if (!existsSync3(candidate)) continue;
|
|
54625
|
+
try {
|
|
54602
54626
|
return readFileSync3(candidate, "utf8");
|
|
54627
|
+
} catch (e) {
|
|
54628
|
+
console.warn(`[kandown] Could not read ${candidate}:`, e.message);
|
|
54603
54629
|
}
|
|
54604
54630
|
}
|
|
54605
54631
|
return "";
|
|
@@ -54607,13 +54633,18 @@ function readAgentDoc(kandownDir) {
|
|
|
54607
54633
|
function moveTaskToColumn(kandownDir, taskId, targetColumn) {
|
|
54608
54634
|
const taskPath = join2(getTasksDir(kandownDir), `${taskId}.md`);
|
|
54609
54635
|
if (!existsSync3(taskPath)) return false;
|
|
54610
|
-
|
|
54611
|
-
|
|
54612
|
-
|
|
54613
|
-
|
|
54614
|
-
|
|
54615
|
-
|
|
54616
|
-
|
|
54636
|
+
try {
|
|
54637
|
+
const parsed = readTask(kandownDir, taskId);
|
|
54638
|
+
writeFileSync2(taskPath, serializeTaskFile({
|
|
54639
|
+
...parsed.frontmatter,
|
|
54640
|
+
id: taskId,
|
|
54641
|
+
status: targetColumn
|
|
54642
|
+
}, parsed.body), "utf8");
|
|
54643
|
+
return true;
|
|
54644
|
+
} catch (e) {
|
|
54645
|
+
console.error(`[kandown] Failed to move task ${taskId} to ${targetColumn}:`, e.message);
|
|
54646
|
+
return false;
|
|
54647
|
+
}
|
|
54617
54648
|
}
|
|
54618
54649
|
|
|
54619
54650
|
// src/cli/lib/daemon.ts
|
|
@@ -56795,7 +56826,13 @@ function launchAgent(opts) {
|
|
|
56795
56826
|
if (!agentDef) {
|
|
56796
56827
|
throw new Error(`Unknown agent: ${agentId}`);
|
|
56797
56828
|
}
|
|
56798
|
-
|
|
56829
|
+
let task;
|
|
56830
|
+
try {
|
|
56831
|
+
task = readTask(kandownDir, taskId);
|
|
56832
|
+
} catch (e) {
|
|
56833
|
+
throw new Error(`Failed to read task ${taskId}: ${e.message}`);
|
|
56834
|
+
}
|
|
56835
|
+
const originalStatus = task.frontmatter.status || "Backlog";
|
|
56799
56836
|
const agentDoc = readAgentDoc(kandownDir);
|
|
56800
56837
|
const taskFileContent = [
|
|
56801
56838
|
`---`,
|
|
@@ -56807,16 +56844,24 @@ function launchAgent(opts) {
|
|
|
56807
56844
|
task.body.trim()
|
|
56808
56845
|
].join("\n");
|
|
56809
56846
|
const { systemPrompt, taskPrompt } = buildPrompt(agentDoc, taskFileContent, taskId, kandownDir);
|
|
56810
|
-
moveTaskToColumn(kandownDir, taskId, "In Progress");
|
|
56847
|
+
const taskMoved = moveTaskToColumn(kandownDir, taskId, "In Progress");
|
|
56848
|
+
if (!taskMoved) {
|
|
56849
|
+
throw new Error(`Could not move task ${taskId} to In Progress \u2014 task file missing or unwritable.`);
|
|
56850
|
+
}
|
|
56811
56851
|
const contextFile = join7(tmpdir(), `kandown-${taskId}-context.md`);
|
|
56812
|
-
|
|
56852
|
+
try {
|
|
56853
|
+
writeFileSync3(contextFile, `${systemPrompt}
|
|
56813
56854
|
|
|
56814
56855
|
---
|
|
56815
56856
|
|
|
56816
56857
|
${taskPrompt}`, "utf8");
|
|
56858
|
+
} catch (e) {
|
|
56859
|
+
console.warn(`[kandown] Failed to write context file (${e.message}); launching anyway.`);
|
|
56860
|
+
}
|
|
56817
56861
|
const launchOpts = { systemPrompt, taskPrompt, kandownDir, taskId };
|
|
56818
56862
|
const [binary, ...args] = agentDef.buildCommand(launchOpts);
|
|
56819
56863
|
if (!binary) {
|
|
56864
|
+
rollbackTaskStatus(kandownDir, taskId, originalStatus);
|
|
56820
56865
|
throw new Error(`Agent ${agentId} returned an empty command`);
|
|
56821
56866
|
}
|
|
56822
56867
|
if (isInTmux()) {
|
|
@@ -56826,24 +56871,47 @@ ${taskPrompt}`, "utf8");
|
|
|
56826
56871
|
`KANDOWN_TASK_ID=${shellescape(taskId)}`,
|
|
56827
56872
|
`KANDOWN_DIR=${shellescape(kandownDir)}`
|
|
56828
56873
|
].join(" ");
|
|
56829
|
-
|
|
56830
|
-
|
|
56831
|
-
|
|
56874
|
+
try {
|
|
56875
|
+
execSync(`tmux split-window -h -p 50 ${shellescape(`env ${envPrefix} ${shellCmd}`)}`, {
|
|
56876
|
+
stdio: "inherit"
|
|
56877
|
+
});
|
|
56878
|
+
} catch (e) {
|
|
56879
|
+
rollbackTaskStatus(kandownDir, taskId, originalStatus);
|
|
56880
|
+
throw new Error(`Failed to open agent pane in tmux: ${e.message}. Is tmux installed and the session valid?`);
|
|
56881
|
+
}
|
|
56832
56882
|
} else {
|
|
56833
|
-
|
|
56834
|
-
|
|
56835
|
-
|
|
56836
|
-
|
|
56837
|
-
|
|
56838
|
-
|
|
56839
|
-
|
|
56840
|
-
|
|
56841
|
-
|
|
56842
|
-
|
|
56843
|
-
|
|
56844
|
-
|
|
56845
|
-
|
|
56846
|
-
|
|
56883
|
+
try {
|
|
56884
|
+
onBeforeExec?.();
|
|
56885
|
+
const child = spawn2(binary, args, {
|
|
56886
|
+
stdio: "inherit",
|
|
56887
|
+
env: {
|
|
56888
|
+
...process.env,
|
|
56889
|
+
// 📖 Expose the context file path so agents that support env vars can use it
|
|
56890
|
+
KANDOWN_CONTEXT_FILE: contextFile,
|
|
56891
|
+
KANDOWN_TASK_ID: taskId,
|
|
56892
|
+
KANDOWN_DIR: kandownDir
|
|
56893
|
+
}
|
|
56894
|
+
});
|
|
56895
|
+
child.on("error", (e) => {
|
|
56896
|
+
rollbackTaskStatus(kandownDir, taskId, originalStatus);
|
|
56897
|
+
console.error(`[kandown] Failed to launch ${agentDef.name}: ${e.message}`);
|
|
56898
|
+
process.exit(1);
|
|
56899
|
+
});
|
|
56900
|
+
child.on("exit", (code) => {
|
|
56901
|
+
if (code === 0) process.exit(0);
|
|
56902
|
+
if (code === null) return;
|
|
56903
|
+
process.exit(code);
|
|
56904
|
+
});
|
|
56905
|
+
} catch (e) {
|
|
56906
|
+
rollbackTaskStatus(kandownDir, taskId, originalStatus);
|
|
56907
|
+
throw new Error(`Failed to launch ${agentDef.name}: ${e.message}`);
|
|
56908
|
+
}
|
|
56909
|
+
}
|
|
56910
|
+
}
|
|
56911
|
+
function rollbackTaskStatus(kandownDir, taskId, originalStatus) {
|
|
56912
|
+
const ok = moveTaskToColumn(kandownDir, taskId, originalStatus);
|
|
56913
|
+
if (!ok) {
|
|
56914
|
+
console.warn(`[kandown] Could not roll back task ${taskId} to ${originalStatus} \u2014 update it manually.`);
|
|
56847
56915
|
}
|
|
56848
56916
|
}
|
|
56849
56917
|
function buildShellCmd(binary, args) {
|
|
@@ -56903,6 +56971,7 @@ function AgentPicker({ agents, taskId, onSelect, onCancel }) {
|
|
|
56903
56971
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: taskId })
|
|
56904
56972
|
] })
|
|
56905
56973
|
] }),
|
|
56974
|
+
agents.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: "No agents installed. Install claude, codex, or opencode." }),
|
|
56906
56975
|
agents.map((agent, idx) => {
|
|
56907
56976
|
const isFocused = idx === cursor;
|
|
56908
56977
|
const numHint = idx < 9 ? `${idx + 1} ` : " ";
|
|
@@ -57280,6 +57349,7 @@ function Board({ kandownDir, version }) {
|
|
|
57280
57349
|
const [rowIndex, setRowIndex] = (0, import_react37.useState)(0);
|
|
57281
57350
|
const [mode, setMode] = (0, import_react37.useState)("browse");
|
|
57282
57351
|
const [statusMsg, setStatusMsg] = (0, import_react37.useState)("");
|
|
57352
|
+
const [boardError, setBoardError] = (0, import_react37.useState)(null);
|
|
57283
57353
|
const [detailTask, setDetailTask] = (0, import_react37.useState)(null);
|
|
57284
57354
|
const [detailTaskId, setDetailTaskId] = (0, import_react37.useState)("");
|
|
57285
57355
|
const [detailScroll, setDetailScroll] = (0, import_react37.useState)(0);
|
|
@@ -57328,43 +57398,49 @@ function Board({ kandownDir, version }) {
|
|
|
57328
57398
|
if (!task) return null;
|
|
57329
57399
|
return { taskId: task.id, colIndex: clickedCol, rowIndex: taskIdx, startX: x, startY: y };
|
|
57330
57400
|
}, [board, columnAtX]);
|
|
57401
|
+
const loadBoardInto = (0, import_react37.useCallback)(() => {
|
|
57402
|
+
try {
|
|
57403
|
+
const loaded = readBoard(kandownDir);
|
|
57404
|
+
setBoard(loaded);
|
|
57405
|
+
updateLayout(loaded);
|
|
57406
|
+
setBoardError(null);
|
|
57407
|
+
return loaded;
|
|
57408
|
+
} catch (e) {
|
|
57409
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
57410
|
+
setBoardError(`Failed to load board: ${msg}`);
|
|
57411
|
+
setStatusMsg("");
|
|
57412
|
+
return null;
|
|
57413
|
+
}
|
|
57414
|
+
}, [kandownDir, updateLayout]);
|
|
57331
57415
|
(0, import_react37.useEffect)(() => {
|
|
57332
|
-
|
|
57333
|
-
setBoard(loaded);
|
|
57334
|
-
updateLayout(loaded);
|
|
57416
|
+
loadBoardInto();
|
|
57335
57417
|
setInstalledAgents(detectInstalledAgents());
|
|
57336
|
-
}, [kandownDir,
|
|
57418
|
+
}, [kandownDir, loadBoardInto]);
|
|
57337
57419
|
(0, import_react37.useEffect)(() => {
|
|
57338
57420
|
const watcher = createWatcher();
|
|
57339
57421
|
watcher.on("taskChanged", () => {
|
|
57340
|
-
|
|
57341
|
-
setBoard(loaded);
|
|
57342
|
-
updateLayout(loaded);
|
|
57422
|
+
loadBoardInto();
|
|
57343
57423
|
});
|
|
57344
57424
|
watcher.on("newTaskDetected", (taskId) => {
|
|
57345
|
-
|
|
57346
|
-
setBoard(loaded);
|
|
57347
|
-
updateLayout(loaded);
|
|
57425
|
+
loadBoardInto();
|
|
57348
57426
|
setStatusMsg(`New task: ${taskId}`);
|
|
57349
57427
|
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57350
57428
|
});
|
|
57351
57429
|
watcher.on("configChanged", () => {
|
|
57352
|
-
|
|
57353
|
-
setBoard(loaded);
|
|
57354
|
-
updateLayout(loaded);
|
|
57430
|
+
loadBoardInto();
|
|
57355
57431
|
});
|
|
57356
57432
|
watcher.start(kandownDir);
|
|
57357
57433
|
return () => {
|
|
57358
57434
|
watcher.stop();
|
|
57359
57435
|
};
|
|
57360
|
-
}, [kandownDir,
|
|
57436
|
+
}, [kandownDir, loadBoardInto]);
|
|
57361
57437
|
const reloadBoard = (0, import_react37.useCallback)(() => {
|
|
57362
|
-
const loaded =
|
|
57363
|
-
|
|
57364
|
-
|
|
57365
|
-
|
|
57366
|
-
|
|
57367
|
-
}, [
|
|
57438
|
+
const loaded = loadBoardInto();
|
|
57439
|
+
if (loaded) {
|
|
57440
|
+
setStatusMsg("Board reloaded");
|
|
57441
|
+
setTimeout(() => setStatusMsg(""), 1500);
|
|
57442
|
+
}
|
|
57443
|
+
}, [loadBoardInto]);
|
|
57368
57444
|
const refreshDaemonStatus = (0, import_react37.useCallback)(async () => {
|
|
57369
57445
|
const next = await getDaemonStatus(kandownDir);
|
|
57370
57446
|
setDaemonStatus(next);
|
|
@@ -57463,11 +57539,16 @@ function Board({ kandownDir, version }) {
|
|
|
57463
57539
|
return col.tasks[Math.min(rowIndex, col.tasks.length - 1)] ?? null;
|
|
57464
57540
|
}, [board, colIndex, rowIndex]);
|
|
57465
57541
|
const openDetail = (0, import_react37.useCallback)((taskId) => {
|
|
57466
|
-
|
|
57467
|
-
|
|
57468
|
-
|
|
57469
|
-
|
|
57470
|
-
|
|
57542
|
+
try {
|
|
57543
|
+
const task = readTask(kandownDir, taskId);
|
|
57544
|
+
setDetailTask(task);
|
|
57545
|
+
setDetailTaskId(taskId);
|
|
57546
|
+
setDetailScroll(0);
|
|
57547
|
+
setMode("detail");
|
|
57548
|
+
} catch (e) {
|
|
57549
|
+
setStatusMsg(`Error opening task: ${e instanceof Error ? e.message : String(e)}`);
|
|
57550
|
+
setTimeout(() => setStatusMsg(""), 4e3);
|
|
57551
|
+
}
|
|
57471
57552
|
}, [kandownDir]);
|
|
57472
57553
|
const closeContextMenu = (0, import_react37.useCallback)(() => {
|
|
57473
57554
|
setCtxMenuRow(-1);
|
|
@@ -57602,9 +57683,7 @@ function Board({ kandownDir, version }) {
|
|
|
57602
57683
|
return;
|
|
57603
57684
|
}
|
|
57604
57685
|
moveTaskToColumn(kandownDir, moveTaskId, targetColName);
|
|
57605
|
-
|
|
57606
|
-
setBoard(loaded);
|
|
57607
|
-
updateLayout(loaded);
|
|
57686
|
+
loadBoardInto();
|
|
57608
57687
|
setStatusMsg(`Moved ${moveTaskId} \u2192 ${targetColName}`);
|
|
57609
57688
|
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57610
57689
|
}
|
|
@@ -57616,7 +57695,7 @@ function Board({ kandownDir, version }) {
|
|
|
57616
57695
|
setMode("browse");
|
|
57617
57696
|
return;
|
|
57618
57697
|
}
|
|
57619
|
-
}, [board, mode, colIndex, rowIndex, ctxMenuRow, moveTaskId, kandownDir, updateLayout, openDetail, closeContextMenu]);
|
|
57698
|
+
}, [board, mode, colIndex, rowIndex, ctxMenuRow, moveTaskId, kandownDir, updateLayout, openDetail, closeContextMenu, loadBoardInto]);
|
|
57620
57699
|
const handleMouseEvent = (0, import_react37.useCallback)((mouse) => {
|
|
57621
57700
|
if (!board) return;
|
|
57622
57701
|
if (mode === "browse") {
|
|
@@ -57678,11 +57757,9 @@ function Board({ kandownDir, version }) {
|
|
|
57678
57757
|
return;
|
|
57679
57758
|
}
|
|
57680
57759
|
moveTaskToColumn(kandownDir, taskDrag.taskId, targetColName);
|
|
57681
|
-
const loaded =
|
|
57682
|
-
setBoard(loaded);
|
|
57683
|
-
updateLayout(loaded);
|
|
57760
|
+
const loaded = loadBoardInto();
|
|
57684
57761
|
setColIndex(targetCol);
|
|
57685
|
-
const movedRow = loaded
|
|
57762
|
+
const movedRow = loaded?.columns[targetCol]?.tasks.findIndex((task) => task.id === taskDrag.taskId) ?? 0;
|
|
57686
57763
|
setRowIndex(Math.max(0, movedRow));
|
|
57687
57764
|
setStatusMsg(`Dragged ${taskDrag.taskId} \u2192 ${targetColName}`);
|
|
57688
57765
|
setTimeout(() => setStatusMsg(""), 2e3);
|
|
@@ -57844,9 +57921,7 @@ function Board({ kandownDir, version }) {
|
|
|
57844
57921
|
return;
|
|
57845
57922
|
}
|
|
57846
57923
|
moveTaskToColumn(kandownDir, moveTaskId, name);
|
|
57847
|
-
|
|
57848
|
-
setBoard(loaded);
|
|
57849
|
-
updateLayout(loaded);
|
|
57924
|
+
loadBoardInto();
|
|
57850
57925
|
setStatusMsg(`Moved ${moveTaskId} \u2192 ${name}`);
|
|
57851
57926
|
setTimeout(() => setStatusMsg(""), 2e3);
|
|
57852
57927
|
}
|
|
@@ -57879,6 +57954,13 @@ function Board({ kandownDir, version }) {
|
|
|
57879
57954
|
}
|
|
57880
57955
|
}
|
|
57881
57956
|
});
|
|
57957
|
+
if (boardError) {
|
|
57958
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", padding: 2, children: [
|
|
57959
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "red", bold: true, children: "Error loading board" }),
|
|
57960
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "red", children: boardError }),
|
|
57961
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: "Press 'r' to retry or 'q' to quit." })
|
|
57962
|
+
] });
|
|
57963
|
+
}
|
|
57882
57964
|
if (!board) {
|
|
57883
57965
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Box_default, { padding: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Text, { color: "gray", children: "Loading board\u2026" }) });
|
|
57884
57966
|
}
|