brainfile 0.19.0 → 0.20.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/README.md +18 -24
- package/dist/__tests__/helpers/mcp-wire.d.ts +45 -0
- package/dist/__tests__/helpers/mcp-wire.d.ts.map +1 -0
- package/dist/__tests__/helpers/mcp-wire.js +133 -0
- package/dist/__tests__/helpers/mcp-wire.js.map +1 -0
- package/dist/{chunk-YTFLC673.mjs → chunk-UOCQBEEW.mjs} +1486 -992
- package/dist/cli.mjs +916 -776
- package/dist/commands/adr.d.ts +1 -9
- package/dist/commands/adr.d.ts.map +1 -1
- package/dist/commands/adr.js +11 -88
- package/dist/commands/adr.js.map +1 -1
- package/dist/commands/archive.d.ts +2 -5
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +43 -11
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/brief.d.ts +31 -0
- package/dist/commands/brief.d.ts.map +1 -0
- package/dist/commands/brief.js +151 -0
- package/dist/commands/brief.js.map +1 -0
- package/dist/commands/complete.js +1 -1
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/log.js +31 -18
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mcp.d.ts +9 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +28 -17
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/migrate.js +10 -12
- package/dist/commands/migrate.js.map +1 -1
- package/dist/mcp/schemas.d.ts +297 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +328 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/tools/brief_tool.d.ts +14 -0
- package/dist/mcp/tools/brief_tool.d.ts.map +1 -0
- package/dist/mcp/tools/brief_tool.js +58 -0
- package/dist/mcp/tools/brief_tool.js.map +1 -0
- package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
- package/dist/mcp/tools/contract_tool.js +37 -13
- package/dist/mcp/tools/contract_tool.js.map +1 -1
- package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
- package/dist/mcp/tools/get_task_tool.js +7 -2
- package/dist/mcp/tools/get_task_tool.js.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.js +9 -3
- package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
- package/dist/mcp/tools/search_tool.d.ts.map +1 -1
- package/dist/mcp/tools/search_tool.js +17 -4
- package/dist/mcp/tools/search_tool.js.map +1 -1
- package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
- package/dist/mcp/tools/subtask_tool.js +47 -13
- package/dist/mcp/tools/subtask_tool.js.map +1 -1
- package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_add_tool.js +7 -2
- package/dist/mcp/tools/task_add_tool.js.map +1 -1
- package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_complete_tool.js +34 -6
- package/dist/mcp/tools/task_complete_tool.js.map +1 -1
- package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_delete_tool.js +7 -2
- package/dist/mcp/tools/task_delete_tool.js.map +1 -1
- package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_move_tool.js +15 -6
- package/dist/mcp/tools/task_move_tool.js.map +1 -1
- package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_patch_tool.js +18 -6
- package/dist/mcp/tools/task_patch_tool.js.map +1 -1
- package/dist/schemas/board.json +1 -1
- package/dist/tui/theme.d.ts +98 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +141 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/{tui-P56H4X7N.mjs → tui-H3RI22S4.mjs} +1593 -1934
- package/dist/utils/cli-error.d.ts +20 -0
- package/dist/utils/cli-error.d.ts.map +1 -1
- package/dist/utils/cli-error.js +36 -0
- package/dist/utils/cli-error.js.map +1 -1
- package/package.json +3 -3
- package/dist/commands/rules.d.ts +0 -68
- package/dist/commands/rules.d.ts.map +0 -1
- package/dist/commands/rules.js +0 -322
- package/dist/commands/rules.js.map +0 -1
|
@@ -5,11 +5,16 @@ const require = __brainfileCreateRequire(import.meta.url);
|
|
|
5
5
|
const __filename = __brainfileFileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = __brainfileDirname(__filename);
|
|
7
7
|
import {
|
|
8
|
+
GLYPHS,
|
|
9
|
+
PALETTE,
|
|
10
|
+
RULE,
|
|
8
11
|
createGitHubIssue,
|
|
9
12
|
createLinearIssue,
|
|
10
13
|
getArchiveConfig,
|
|
14
|
+
getContractStateColor,
|
|
11
15
|
getEffectiveDestination,
|
|
12
16
|
getLinearTeams,
|
|
17
|
+
getTypeGlyph,
|
|
13
18
|
import_core,
|
|
14
19
|
isGitHubAuthenticated,
|
|
15
20
|
isLinearAuthenticated,
|
|
@@ -18,7 +23,7 @@ import {
|
|
|
18
23
|
require_source,
|
|
19
24
|
resolveCliBrainfilePath,
|
|
20
25
|
shouldSuggestV2Migration
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-UOCQBEEW.mjs";
|
|
22
27
|
import {
|
|
23
28
|
__commonJS,
|
|
24
29
|
__export,
|
|
@@ -406,8 +411,8 @@ var require_react_production = __commonJS({
|
|
|
406
411
|
exports.use = function(usable) {
|
|
407
412
|
return ReactSharedInternals.H.use(usable);
|
|
408
413
|
};
|
|
409
|
-
exports.useActionState = function(action,
|
|
410
|
-
return ReactSharedInternals.H.useActionState(action,
|
|
414
|
+
exports.useActionState = function(action, initialState, permalink) {
|
|
415
|
+
return ReactSharedInternals.H.useActionState(action, initialState, permalink);
|
|
411
416
|
};
|
|
412
417
|
exports.useCallback = function(callback, deps) {
|
|
413
418
|
return ReactSharedInternals.H.useCallback(callback, deps);
|
|
@@ -450,8 +455,8 @@ var require_react_production = __commonJS({
|
|
|
450
455
|
exports.useRef = function(initialValue) {
|
|
451
456
|
return ReactSharedInternals.H.useRef(initialValue);
|
|
452
457
|
};
|
|
453
|
-
exports.useState = function(
|
|
454
|
-
return ReactSharedInternals.H.useState(
|
|
458
|
+
exports.useState = function(initialState) {
|
|
459
|
+
return ReactSharedInternals.H.useState(initialState);
|
|
455
460
|
};
|
|
456
461
|
exports.useSyncExternalStore = function(subscribe, getSnapshot, getServerSnapshot) {
|
|
457
462
|
return ReactSharedInternals.H.useSyncExternalStore(
|
|
@@ -3227,11 +3232,11 @@ var require_react_reconciler_production = __commonJS({
|
|
|
3227
3232
|
var root = enqueueConcurrentRenderForLane(fiber, 2);
|
|
3228
3233
|
null !== root && scheduleUpdateOnFiber(root, fiber, 2);
|
|
3229
3234
|
}
|
|
3230
|
-
function mountStateImpl(
|
|
3235
|
+
function mountStateImpl(initialState) {
|
|
3231
3236
|
var hook = mountWorkInProgressHook();
|
|
3232
|
-
if ("function" === typeof
|
|
3233
|
-
var initialStateInitializer =
|
|
3234
|
-
|
|
3237
|
+
if ("function" === typeof initialState) {
|
|
3238
|
+
var initialStateInitializer = initialState;
|
|
3239
|
+
initialState = initialStateInitializer();
|
|
3235
3240
|
if (shouldDoubleInvokeUserFnsInHooksDEV) {
|
|
3236
3241
|
setIsStrictModeForDevtools(true);
|
|
3237
3242
|
try {
|
|
@@ -3241,13 +3246,13 @@ var require_react_reconciler_production = __commonJS({
|
|
|
3241
3246
|
}
|
|
3242
3247
|
}
|
|
3243
3248
|
}
|
|
3244
|
-
hook.memoizedState = hook.baseState =
|
|
3249
|
+
hook.memoizedState = hook.baseState = initialState;
|
|
3245
3250
|
hook.queue = {
|
|
3246
3251
|
pending: null,
|
|
3247
3252
|
lanes: 0,
|
|
3248
3253
|
dispatch: null,
|
|
3249
3254
|
lastRenderedReducer: basicStateReducer,
|
|
3250
|
-
lastRenderedState:
|
|
3255
|
+
lastRenderedState: initialState
|
|
3251
3256
|
};
|
|
3252
3257
|
return hook;
|
|
3253
3258
|
}
|
|
@@ -8378,14 +8383,14 @@ var require_react_reconciler_production = __commonJS({
|
|
|
8378
8383
|
}
|
|
8379
8384
|
var exports2 = {};
|
|
8380
8385
|
"use strict";
|
|
8381
|
-
var
|
|
8386
|
+
var React20 = require_react(), Scheduler2 = require_scheduler(), assign = Object.assign, REACT_LEGACY_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.element"), REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for("react.memo"), REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for("react.lazy");
|
|
8382
8387
|
/* @__PURE__ */ Symbol.for("react.scope");
|
|
8383
8388
|
var REACT_ACTIVITY_TYPE = /* @__PURE__ */ Symbol.for("react.activity");
|
|
8384
8389
|
/* @__PURE__ */ Symbol.for("react.legacy_hidden");
|
|
8385
8390
|
/* @__PURE__ */ Symbol.for("react.tracing_marker");
|
|
8386
8391
|
var REACT_MEMO_CACHE_SENTINEL = /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel");
|
|
8387
8392
|
/* @__PURE__ */ Symbol.for("react.view_transition");
|
|
8388
|
-
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = /* @__PURE__ */ Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals =
|
|
8393
|
+
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator, REACT_CLIENT_REFERENCE = /* @__PURE__ */ Symbol.for("react.client.reference"), isArrayImpl = Array.isArray, ReactSharedInternals = React20.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, rendererVersion = $$$config.rendererVersion, rendererPackageName = $$$config.rendererPackageName, extraDevToolsConfig = $$$config.extraDevToolsConfig, getPublicInstance = $$$config.getPublicInstance, getRootHostContext = $$$config.getRootHostContext, getChildHostContext = $$$config.getChildHostContext, prepareForCommit = $$$config.prepareForCommit, resetAfterCommit = $$$config.resetAfterCommit, createInstance = $$$config.createInstance;
|
|
8389
8394
|
$$$config.cloneMutableInstance;
|
|
8390
8395
|
var appendInitialChild = $$$config.appendInitialChild, finalizeInitialChildren = $$$config.finalizeInitialChildren, shouldSetTextContent = $$$config.shouldSetTextContent, createTextInstance = $$$config.createTextInstance;
|
|
8391
8396
|
$$$config.cloneMutableTextInstance;
|
|
@@ -8547,7 +8552,7 @@ var require_react_reconciler_production = __commonJS({
|
|
|
8547
8552
|
useReducer: function(reducer, initialArg, init) {
|
|
8548
8553
|
var hook = mountWorkInProgressHook();
|
|
8549
8554
|
if (void 0 !== init) {
|
|
8550
|
-
var
|
|
8555
|
+
var initialState = init(initialArg);
|
|
8551
8556
|
if (shouldDoubleInvokeUserFnsInHooksDEV) {
|
|
8552
8557
|
setIsStrictModeForDevtools(true);
|
|
8553
8558
|
try {
|
|
@@ -8556,14 +8561,14 @@ var require_react_reconciler_production = __commonJS({
|
|
|
8556
8561
|
setIsStrictModeForDevtools(false);
|
|
8557
8562
|
}
|
|
8558
8563
|
}
|
|
8559
|
-
} else
|
|
8560
|
-
hook.memoizedState = hook.baseState =
|
|
8564
|
+
} else initialState = initialArg;
|
|
8565
|
+
hook.memoizedState = hook.baseState = initialState;
|
|
8561
8566
|
reducer = {
|
|
8562
8567
|
pending: null,
|
|
8563
8568
|
lanes: 0,
|
|
8564
8569
|
dispatch: null,
|
|
8565
8570
|
lastRenderedReducer: reducer,
|
|
8566
|
-
lastRenderedState:
|
|
8571
|
+
lastRenderedState: initialState
|
|
8567
8572
|
};
|
|
8568
8573
|
hook.queue = reducer;
|
|
8569
8574
|
reducer = reducer.dispatch = dispatchReducerAction.bind(
|
|
@@ -8578,15 +8583,15 @@ var require_react_reconciler_production = __commonJS({
|
|
|
8578
8583
|
initialValue = { current: initialValue };
|
|
8579
8584
|
return hook.memoizedState = initialValue;
|
|
8580
8585
|
},
|
|
8581
|
-
useState: function(
|
|
8582
|
-
|
|
8583
|
-
var queue =
|
|
8586
|
+
useState: function(initialState) {
|
|
8587
|
+
initialState = mountStateImpl(initialState);
|
|
8588
|
+
var queue = initialState.queue, dispatch = dispatchSetState.bind(
|
|
8584
8589
|
null,
|
|
8585
8590
|
currentlyRenderingFiber,
|
|
8586
8591
|
queue
|
|
8587
8592
|
);
|
|
8588
8593
|
queue.dispatch = dispatch;
|
|
8589
|
-
return [
|
|
8594
|
+
return [initialState.memoizedState, dispatch];
|
|
8590
8595
|
},
|
|
8591
8596
|
useDebugValue: mountDebugValue,
|
|
8592
8597
|
useDeferredValue: function(value, initialValue) {
|
|
@@ -9498,7 +9503,7 @@ var require_stack_utils = __commonJS({
|
|
|
9498
9503
|
});
|
|
9499
9504
|
|
|
9500
9505
|
// src/commands/tui.tsx
|
|
9501
|
-
var
|
|
9506
|
+
var import_react44 = __toESM(require_react());
|
|
9502
9507
|
|
|
9503
9508
|
// node_modules/ink/build/render.js
|
|
9504
9509
|
import { Stream } from "node:stream";
|
|
@@ -14501,8 +14506,8 @@ var cleanupYogaNode = (node) => {
|
|
|
14501
14506
|
var currentUpdatePriority = import_constants.NoEventPriority;
|
|
14502
14507
|
var currentRootNode;
|
|
14503
14508
|
async function loadPackageJson() {
|
|
14504
|
-
const
|
|
14505
|
-
const content =
|
|
14509
|
+
const fs7 = await import("node:fs");
|
|
14510
|
+
const content = fs7.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
14506
14511
|
const parsedContent = JSON.parse(content);
|
|
14507
14512
|
return {
|
|
14508
14513
|
name: parsedContent?.name,
|
|
@@ -17092,8 +17097,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
17092
17097
|
}
|
|
17093
17098
|
|
|
17094
17099
|
// node_modules/ink/build/components/ErrorOverview.js
|
|
17095
|
-
var cleanupPath = (
|
|
17096
|
-
return
|
|
17100
|
+
var cleanupPath = (path4) => {
|
|
17101
|
+
return path4?.replace(`file://${cwd()}/`, "");
|
|
17097
17102
|
};
|
|
17098
17103
|
var stackUtils = new import_stack_utils.default({
|
|
17099
17104
|
cwd: cwd(),
|
|
@@ -19061,7 +19066,9 @@ var parse_keypress_default = parseKeypress;
|
|
|
19061
19066
|
|
|
19062
19067
|
// node_modules/ink/build/hooks/use-stdin.js
|
|
19063
19068
|
var import_react21 = __toESM(require_react(), 1);
|
|
19069
|
+
var useStdin = () => (0, import_react21.useContext)(StdinContext_default);
|
|
19064
19070
|
var useStdinContext = () => (0, import_react21.useContext)(StdinContext_default);
|
|
19071
|
+
var use_stdin_default = useStdin;
|
|
19065
19072
|
|
|
19066
19073
|
// node_modules/ink/build/hooks/use-input.js
|
|
19067
19074
|
var useInput = (inputHandler, options = {}) => {
|
|
@@ -19182,86 +19189,71 @@ var import_react33 = __toESM(require_react(), 1);
|
|
|
19182
19189
|
|
|
19183
19190
|
// src/commands/tui.tsx
|
|
19184
19191
|
var import_chalk4 = __toESM(require_source());
|
|
19185
|
-
import * as
|
|
19192
|
+
import * as fs6 from "fs";
|
|
19186
19193
|
|
|
19187
19194
|
// src/tui/BrainfileTUI.tsx
|
|
19188
|
-
var
|
|
19189
|
-
var
|
|
19195
|
+
var import_react43 = __toESM(require_react());
|
|
19196
|
+
var import_core6 = __toESM(require_dist());
|
|
19190
19197
|
|
|
19191
|
-
// src/tui/
|
|
19192
|
-
var
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
|
|
19198
|
+
// src/tui/copy.ts
|
|
19199
|
+
var STATUS = {
|
|
19200
|
+
completed: (id) => `Completed ${id}`,
|
|
19201
|
+
deleted: (id) => `Deleted ${id}`,
|
|
19202
|
+
moved: (column) => `Moved to ${column}`,
|
|
19203
|
+
added: (id) => `Added ${id}`,
|
|
19204
|
+
addedGeneric: "Added",
|
|
19205
|
+
titleRequired: "Title required",
|
|
19206
|
+
completeCancelled: "Complete cancelled",
|
|
19207
|
+
deleteCancelled: "Delete cancelled",
|
|
19208
|
+
readOnly: "Completed documents are read-only here",
|
|
19209
|
+
alreadyCompleted: "Already completed",
|
|
19210
|
+
cannotComplete: (id) => `${id} cannot be completed`,
|
|
19211
|
+
noDocument: "No document selected",
|
|
19212
|
+
noParent: "No parent",
|
|
19213
|
+
noSubtasks: "No subtasks",
|
|
19214
|
+
reloaded: "Reloaded",
|
|
19215
|
+
toggled: (id) => `Toggled ${id}`,
|
|
19216
|
+
copied: (id) => `Copied ${id}`,
|
|
19217
|
+
copyFailed: "Copy failed",
|
|
19218
|
+
editorNeedsTty: "$EDITOR needs an interactive terminal",
|
|
19219
|
+
editorFailed: (msg) => `Editor failed: ${msg}`,
|
|
19220
|
+
completeFailed: "Could not complete",
|
|
19221
|
+
deleteFailed: "Could not delete",
|
|
19222
|
+
moveFailed: "Could not move",
|
|
19223
|
+
addFailed: "Could not add",
|
|
19224
|
+
toggleFailed: "Could not toggle",
|
|
19225
|
+
priorityFailed: "Could not update priority",
|
|
19226
|
+
priorityUpdated: (priority) => `Priority: ${priority}`,
|
|
19227
|
+
archived: (id) => `Archived ${id}`,
|
|
19228
|
+
archiving: "Archiving\u2026",
|
|
19229
|
+
archiveFailed: "Could not archive"
|
|
19223
19230
|
};
|
|
19224
|
-
var
|
|
19225
|
-
|
|
19226
|
-
|
|
19227
|
-
|
|
19228
|
-
|
|
19229
|
-
|
|
19230
|
-
task: ""
|
|
19231
|
+
var EMPTY = {
|
|
19232
|
+
noMatches: "No matches \xB7 esc to clear",
|
|
19233
|
+
nothingCompleted: "Nothing completed yet \xB7 L board",
|
|
19234
|
+
emptyColumn: "No documents in this column \xB7 a add",
|
|
19235
|
+
noType: (type) => `No ${type} documents`,
|
|
19236
|
+
noDocuments: "No documents"
|
|
19231
19237
|
};
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
success: "\u2713",
|
|
19241
|
-
error: "\u2717",
|
|
19242
|
-
warning: "\u26A0",
|
|
19243
|
-
live: "\u25CF",
|
|
19244
|
-
subtaskOpen: "\u25FB",
|
|
19245
|
-
subtaskDone: "\u2611",
|
|
19246
|
-
orphanParent: "\u2190"
|
|
19238
|
+
var CHROME = {
|
|
19239
|
+
filterAffordance: "/ filter ? help",
|
|
19240
|
+
helpTitle: "help",
|
|
19241
|
+
helpDismiss: "any key dismisses \xB7 q quit",
|
|
19242
|
+
tooSmall: "Terminal too small",
|
|
19243
|
+
loading: "Loading\u2026",
|
|
19244
|
+
errorTitle: "Error",
|
|
19245
|
+
errorHint: "q quit \xB7 r retry"
|
|
19247
19246
|
};
|
|
19248
|
-
function
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
case "failed":
|
|
19259
|
-
case "blocked":
|
|
19260
|
-
return PALETTE.contractFailed;
|
|
19261
|
-
case "draft":
|
|
19262
|
-
default:
|
|
19263
|
-
return PALETTE.contractDraft;
|
|
19264
|
-
}
|
|
19247
|
+
function matchCountLabel(matchCount, totalCount, query) {
|
|
19248
|
+
if (!query) return "";
|
|
19249
|
+
const word = matchCount === 1 ? "match" : "matches";
|
|
19250
|
+
return `${matchCount}/${totalCount} ${word} "${query}"`;
|
|
19251
|
+
}
|
|
19252
|
+
function itemCountLabel(n) {
|
|
19253
|
+
return `${n} item${n === 1 ? "" : "s"}`;
|
|
19254
|
+
}
|
|
19255
|
+
function incompleteChildrenLabel(n) {
|
|
19256
|
+
return `${n} incomplete child task${n === 1 ? "" : "s"}:`;
|
|
19265
19257
|
}
|
|
19266
19258
|
|
|
19267
19259
|
// src/tui/types.ts
|
|
@@ -19277,7 +19269,7 @@ var HEADER_ROWS = 2;
|
|
|
19277
19269
|
var FOOTER_ROWS = 3;
|
|
19278
19270
|
|
|
19279
19271
|
// src/tui/rows.ts
|
|
19280
|
-
function buildRows(tasks) {
|
|
19272
|
+
function buildRows(tasks, collapsedIds) {
|
|
19281
19273
|
const visibleIds = new Set(tasks.map((t) => t.id));
|
|
19282
19274
|
const emitted = /* @__PURE__ */ new Set();
|
|
19283
19275
|
const rows = [];
|
|
@@ -19295,12 +19287,20 @@ function buildRows(tasks) {
|
|
|
19295
19287
|
const parentVisible = Boolean(parentId && parentId !== task.id && visibleIds.has(parentId));
|
|
19296
19288
|
if (parentVisible) continue;
|
|
19297
19289
|
emitted.add(task.id);
|
|
19290
|
+
const children = childrenOf.get(task.id) ?? [];
|
|
19291
|
+
const isCollapsed = children.length > 0 && Boolean(collapsedIds?.has(task.id));
|
|
19298
19292
|
rows.push({
|
|
19299
19293
|
task,
|
|
19300
19294
|
depth: 0,
|
|
19301
|
-
orphanParentId: parentId && !parentVisible ? parentId : void 0
|
|
19295
|
+
orphanParentId: parentId && !parentVisible ? parentId : void 0,
|
|
19296
|
+
childCount: children.length > 0 ? children.length : void 0,
|
|
19297
|
+
collapsed: children.length > 0 ? isCollapsed : void 0
|
|
19302
19298
|
});
|
|
19303
|
-
|
|
19299
|
+
if (isCollapsed) {
|
|
19300
|
+
for (const child of children) emitted.add(child.id);
|
|
19301
|
+
continue;
|
|
19302
|
+
}
|
|
19303
|
+
for (const child of children) {
|
|
19304
19304
|
if (emitted.has(child.id)) continue;
|
|
19305
19305
|
emitted.add(child.id);
|
|
19306
19306
|
rows.push({ task: child, depth: 1 });
|
|
@@ -19313,6 +19313,13 @@ function buildRows(tasks) {
|
|
|
19313
19313
|
}
|
|
19314
19314
|
return rows;
|
|
19315
19315
|
}
|
|
19316
|
+
function buildFlatRows(tasks) {
|
|
19317
|
+
return tasks.map((task) => ({
|
|
19318
|
+
task,
|
|
19319
|
+
depth: 0,
|
|
19320
|
+
orphanParentId: task.parentId && task.parentId !== task.id ? task.parentId : void 0
|
|
19321
|
+
}));
|
|
19322
|
+
}
|
|
19316
19323
|
function windowRows(rows, selectedIndex, viewportHeight) {
|
|
19317
19324
|
if (rows.length <= viewportHeight) return { visible: rows, start: 0 };
|
|
19318
19325
|
const half = Math.floor(viewportHeight / 2);
|
|
@@ -19321,10 +19328,89 @@ function windowRows(rows, selectedIndex, viewportHeight) {
|
|
|
19321
19328
|
return { visible: rows.slice(start, start + viewportHeight), start };
|
|
19322
19329
|
}
|
|
19323
19330
|
|
|
19331
|
+
// src/tui/utils.ts
|
|
19332
|
+
var import_core2 = __toESM(require_dist());
|
|
19333
|
+
|
|
19334
|
+
// src/tui/text.ts
|
|
19335
|
+
function truncate(text, maxWidth) {
|
|
19336
|
+
if (!text) return " ";
|
|
19337
|
+
if (maxWidth <= 0) return " ";
|
|
19338
|
+
if (maxWidth <= 3) return text.slice(0, maxWidth) || " ";
|
|
19339
|
+
if (text.length <= maxWidth) return text;
|
|
19340
|
+
return `${text.slice(0, maxWidth - 1)}\u2026`;
|
|
19341
|
+
}
|
|
19342
|
+
function truncateStart(text, maxWidth) {
|
|
19343
|
+
if (!text) return " ";
|
|
19344
|
+
if (maxWidth <= 0) return " ";
|
|
19345
|
+
if (maxWidth <= 1) return text.slice(-1) || " ";
|
|
19346
|
+
if (text.length <= maxWidth) return text;
|
|
19347
|
+
return `\u2026${text.slice(text.length - (maxWidth - 1))}`;
|
|
19348
|
+
}
|
|
19349
|
+
function wrapText2(text, maxWidth) {
|
|
19350
|
+
if (!text || maxWidth <= 0) return [];
|
|
19351
|
+
const words = text.split(/\s+/);
|
|
19352
|
+
const lines = [];
|
|
19353
|
+
let currentLine = "";
|
|
19354
|
+
for (const word of words) {
|
|
19355
|
+
if (word.length > maxWidth) {
|
|
19356
|
+
if (currentLine) {
|
|
19357
|
+
lines.push(currentLine);
|
|
19358
|
+
currentLine = "";
|
|
19359
|
+
}
|
|
19360
|
+
for (let i = 0; i < word.length; i += maxWidth) {
|
|
19361
|
+
lines.push(word.slice(i, i + maxWidth));
|
|
19362
|
+
}
|
|
19363
|
+
} else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
19364
|
+
currentLine = currentLine ? `${currentLine} ${word}` : word;
|
|
19365
|
+
} else {
|
|
19366
|
+
if (currentLine) lines.push(currentLine);
|
|
19367
|
+
currentLine = word;
|
|
19368
|
+
}
|
|
19369
|
+
}
|
|
19370
|
+
if (currentLine) lines.push(currentLine);
|
|
19371
|
+
return lines.length > 0 ? lines : [""];
|
|
19372
|
+
}
|
|
19373
|
+
function pad(n) {
|
|
19374
|
+
return " ".repeat(Math.max(0, n));
|
|
19375
|
+
}
|
|
19376
|
+
|
|
19377
|
+
// src/tui/utils.ts
|
|
19378
|
+
function getPriorityColor(priority) {
|
|
19379
|
+
switch (priority?.toLowerCase()) {
|
|
19380
|
+
case "critical":
|
|
19381
|
+
return PALETTE.critical;
|
|
19382
|
+
case "high":
|
|
19383
|
+
return PALETTE.high;
|
|
19384
|
+
case "medium":
|
|
19385
|
+
return PALETTE.medium;
|
|
19386
|
+
case "low":
|
|
19387
|
+
return PALETTE.low;
|
|
19388
|
+
default:
|
|
19389
|
+
return PALETTE.textDim;
|
|
19390
|
+
}
|
|
19391
|
+
}
|
|
19392
|
+
function getDocType(task) {
|
|
19393
|
+
return (task.type || "task").toLowerCase();
|
|
19394
|
+
}
|
|
19395
|
+
function isCompletable(task) {
|
|
19396
|
+
if (!task) return false;
|
|
19397
|
+
return getDocType(task) !== "adr";
|
|
19398
|
+
}
|
|
19399
|
+
function getSubtaskProgress(task) {
|
|
19400
|
+
const subtasks = task.subtasks ?? [];
|
|
19401
|
+
if (subtasks.length === 0) return void 0;
|
|
19402
|
+
const done = subtasks.filter((s) => s.completed).length;
|
|
19403
|
+
return `${done}/${subtasks.length}`;
|
|
19404
|
+
}
|
|
19405
|
+
function getContractState(task) {
|
|
19406
|
+
const contract = task.contract;
|
|
19407
|
+
return contract?.status;
|
|
19408
|
+
}
|
|
19409
|
+
|
|
19324
19410
|
// src/tui/hooks/useBrainfileLoader.ts
|
|
19325
19411
|
var import_react34 = __toESM(require_react());
|
|
19326
|
-
import * as
|
|
19327
|
-
import * as
|
|
19412
|
+
import * as fs4 from "fs";
|
|
19413
|
+
import * as path2 from "path";
|
|
19328
19414
|
|
|
19329
19415
|
// ../node_modules/chokidar/esm/index.js
|
|
19330
19416
|
import { stat as statcb } from "fs";
|
|
@@ -19402,7 +19488,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
19402
19488
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
19403
19489
|
const statMethod = opts.lstat ? lstat : stat;
|
|
19404
19490
|
if (wantBigintFsStats) {
|
|
19405
|
-
this._stat = (
|
|
19491
|
+
this._stat = (path4) => statMethod(path4, { bigint: true });
|
|
19406
19492
|
} else {
|
|
19407
19493
|
this._stat = statMethod;
|
|
19408
19494
|
}
|
|
@@ -19427,8 +19513,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
19427
19513
|
const par = this.parent;
|
|
19428
19514
|
const fil = par && par.files;
|
|
19429
19515
|
if (fil && fil.length > 0) {
|
|
19430
|
-
const { path:
|
|
19431
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
19516
|
+
const { path: path4, depth } = par;
|
|
19517
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path4));
|
|
19432
19518
|
const awaited = await Promise.all(slice);
|
|
19433
19519
|
for (const entry of awaited) {
|
|
19434
19520
|
if (!entry)
|
|
@@ -19468,20 +19554,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
19468
19554
|
this.reading = false;
|
|
19469
19555
|
}
|
|
19470
19556
|
}
|
|
19471
|
-
async _exploreDir(
|
|
19557
|
+
async _exploreDir(path4, depth) {
|
|
19472
19558
|
let files;
|
|
19473
19559
|
try {
|
|
19474
|
-
files = await readdir(
|
|
19560
|
+
files = await readdir(path4, this._rdOptions);
|
|
19475
19561
|
} catch (error) {
|
|
19476
19562
|
this._onError(error);
|
|
19477
19563
|
}
|
|
19478
|
-
return { files, depth, path:
|
|
19564
|
+
return { files, depth, path: path4 };
|
|
19479
19565
|
}
|
|
19480
|
-
async _formatEntry(dirent,
|
|
19566
|
+
async _formatEntry(dirent, path4) {
|
|
19481
19567
|
let entry;
|
|
19482
19568
|
const basename4 = this._isDirent ? dirent.name : dirent;
|
|
19483
19569
|
try {
|
|
19484
|
-
const fullPath = presolve(pjoin(
|
|
19570
|
+
const fullPath = presolve(pjoin(path4, basename4));
|
|
19485
19571
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
|
|
19486
19572
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
19487
19573
|
} catch (err) {
|
|
@@ -19881,16 +19967,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
19881
19967
|
};
|
|
19882
19968
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
19883
19969
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
19884
|
-
function createFsWatchInstance(
|
|
19970
|
+
function createFsWatchInstance(path4, options, listener, errHandler, emitRaw) {
|
|
19885
19971
|
const handleEvent = (rawEvent, evPath) => {
|
|
19886
|
-
listener(
|
|
19887
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
19888
|
-
if (evPath &&
|
|
19889
|
-
fsWatchBroadcast(sysPath.resolve(
|
|
19972
|
+
listener(path4);
|
|
19973
|
+
emitRaw(rawEvent, evPath, { watchedPath: path4 });
|
|
19974
|
+
if (evPath && path4 !== evPath) {
|
|
19975
|
+
fsWatchBroadcast(sysPath.resolve(path4, evPath), KEY_LISTENERS, sysPath.join(path4, evPath));
|
|
19890
19976
|
}
|
|
19891
19977
|
};
|
|
19892
19978
|
try {
|
|
19893
|
-
return fs_watch(
|
|
19979
|
+
return fs_watch(path4, {
|
|
19894
19980
|
persistent: options.persistent
|
|
19895
19981
|
}, handleEvent);
|
|
19896
19982
|
} catch (error) {
|
|
@@ -19906,12 +19992,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
19906
19992
|
listener(val1, val2, val3);
|
|
19907
19993
|
});
|
|
19908
19994
|
};
|
|
19909
|
-
var setFsWatchListener = (
|
|
19995
|
+
var setFsWatchListener = (path4, fullPath, options, handlers) => {
|
|
19910
19996
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
19911
19997
|
let cont = FsWatchInstances.get(fullPath);
|
|
19912
19998
|
let watcher;
|
|
19913
19999
|
if (!options.persistent) {
|
|
19914
|
-
watcher = createFsWatchInstance(
|
|
20000
|
+
watcher = createFsWatchInstance(path4, options, listener, errHandler, rawEmitter);
|
|
19915
20001
|
if (!watcher)
|
|
19916
20002
|
return;
|
|
19917
20003
|
return watcher.close.bind(watcher);
|
|
@@ -19922,7 +20008,7 @@ var setFsWatchListener = (path3, fullPath, options, handlers) => {
|
|
|
19922
20008
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
19923
20009
|
} else {
|
|
19924
20010
|
watcher = createFsWatchInstance(
|
|
19925
|
-
|
|
20011
|
+
path4,
|
|
19926
20012
|
options,
|
|
19927
20013
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
19928
20014
|
errHandler,
|
|
@@ -19937,7 +20023,7 @@ var setFsWatchListener = (path3, fullPath, options, handlers) => {
|
|
|
19937
20023
|
cont.watcherUnusable = true;
|
|
19938
20024
|
if (isWindows3 && error.code === "EPERM") {
|
|
19939
20025
|
try {
|
|
19940
|
-
const fd = await open(
|
|
20026
|
+
const fd = await open(path4, "r");
|
|
19941
20027
|
await fd.close();
|
|
19942
20028
|
broadcastErr(error);
|
|
19943
20029
|
} catch (err) {
|
|
@@ -19968,7 +20054,7 @@ var setFsWatchListener = (path3, fullPath, options, handlers) => {
|
|
|
19968
20054
|
};
|
|
19969
20055
|
};
|
|
19970
20056
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
19971
|
-
var setFsWatchFileListener = (
|
|
20057
|
+
var setFsWatchFileListener = (path4, fullPath, options, handlers) => {
|
|
19972
20058
|
const { listener, rawEmitter } = handlers;
|
|
19973
20059
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
19974
20060
|
const copts = cont && cont.options;
|
|
@@ -19990,7 +20076,7 @@ var setFsWatchFileListener = (path3, fullPath, options, handlers) => {
|
|
|
19990
20076
|
});
|
|
19991
20077
|
const currmtime = curr.mtimeMs;
|
|
19992
20078
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
19993
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
20079
|
+
foreach(cont.listeners, (listener2) => listener2(path4, curr));
|
|
19994
20080
|
}
|
|
19995
20081
|
})
|
|
19996
20082
|
};
|
|
@@ -20018,13 +20104,13 @@ var NodeFsHandler = class {
|
|
|
20018
20104
|
* @param listener on fs change
|
|
20019
20105
|
* @returns closer for the watcher instance
|
|
20020
20106
|
*/
|
|
20021
|
-
_watchWithNodeFs(
|
|
20107
|
+
_watchWithNodeFs(path4, listener) {
|
|
20022
20108
|
const opts = this.fsw.options;
|
|
20023
|
-
const directory = sysPath.dirname(
|
|
20024
|
-
const basename4 = sysPath.basename(
|
|
20109
|
+
const directory = sysPath.dirname(path4);
|
|
20110
|
+
const basename4 = sysPath.basename(path4);
|
|
20025
20111
|
const parent = this.fsw._getWatchedDir(directory);
|
|
20026
20112
|
parent.add(basename4);
|
|
20027
|
-
const absolutePath = sysPath.resolve(
|
|
20113
|
+
const absolutePath = sysPath.resolve(path4);
|
|
20028
20114
|
const options = {
|
|
20029
20115
|
persistent: opts.persistent
|
|
20030
20116
|
};
|
|
@@ -20034,12 +20120,12 @@ var NodeFsHandler = class {
|
|
|
20034
20120
|
if (opts.usePolling) {
|
|
20035
20121
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
20036
20122
|
options.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
|
|
20037
|
-
closer = setFsWatchFileListener(
|
|
20123
|
+
closer = setFsWatchFileListener(path4, absolutePath, options, {
|
|
20038
20124
|
listener,
|
|
20039
20125
|
rawEmitter: this.fsw._emitRaw
|
|
20040
20126
|
});
|
|
20041
20127
|
} else {
|
|
20042
|
-
closer = setFsWatchListener(
|
|
20128
|
+
closer = setFsWatchListener(path4, absolutePath, options, {
|
|
20043
20129
|
listener,
|
|
20044
20130
|
errHandler: this._boundHandleError,
|
|
20045
20131
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -20061,7 +20147,7 @@ var NodeFsHandler = class {
|
|
|
20061
20147
|
let prevStats = stats;
|
|
20062
20148
|
if (parent.has(basename4))
|
|
20063
20149
|
return;
|
|
20064
|
-
const listener = async (
|
|
20150
|
+
const listener = async (path4, newStats) => {
|
|
20065
20151
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
20066
20152
|
return;
|
|
20067
20153
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -20075,11 +20161,11 @@ var NodeFsHandler = class {
|
|
|
20075
20161
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
20076
20162
|
}
|
|
20077
20163
|
if ((isMacos || isLinux2 || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
20078
|
-
this.fsw._closeFile(
|
|
20164
|
+
this.fsw._closeFile(path4);
|
|
20079
20165
|
prevStats = newStats2;
|
|
20080
20166
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
20081
20167
|
if (closer2)
|
|
20082
|
-
this.fsw._addPathCloser(
|
|
20168
|
+
this.fsw._addPathCloser(path4, closer2);
|
|
20083
20169
|
} else {
|
|
20084
20170
|
prevStats = newStats2;
|
|
20085
20171
|
}
|
|
@@ -20111,7 +20197,7 @@ var NodeFsHandler = class {
|
|
|
20111
20197
|
* @param item basename of this item
|
|
20112
20198
|
* @returns true if no more processing is needed for this entry.
|
|
20113
20199
|
*/
|
|
20114
|
-
async _handleSymlink(entry, directory,
|
|
20200
|
+
async _handleSymlink(entry, directory, path4, item) {
|
|
20115
20201
|
if (this.fsw.closed) {
|
|
20116
20202
|
return;
|
|
20117
20203
|
}
|
|
@@ -20121,7 +20207,7 @@ var NodeFsHandler = class {
|
|
|
20121
20207
|
this.fsw._incrReadyCount();
|
|
20122
20208
|
let linkPath;
|
|
20123
20209
|
try {
|
|
20124
|
-
linkPath = await fsrealpath(
|
|
20210
|
+
linkPath = await fsrealpath(path4);
|
|
20125
20211
|
} catch (e) {
|
|
20126
20212
|
this.fsw._emitReady();
|
|
20127
20213
|
return true;
|
|
@@ -20131,12 +20217,12 @@ var NodeFsHandler = class {
|
|
|
20131
20217
|
if (dir.has(item)) {
|
|
20132
20218
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
20133
20219
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
20134
|
-
this.fsw._emit(EV.CHANGE,
|
|
20220
|
+
this.fsw._emit(EV.CHANGE, path4, entry.stats);
|
|
20135
20221
|
}
|
|
20136
20222
|
} else {
|
|
20137
20223
|
dir.add(item);
|
|
20138
20224
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
20139
|
-
this.fsw._emit(EV.ADD,
|
|
20225
|
+
this.fsw._emit(EV.ADD, path4, entry.stats);
|
|
20140
20226
|
}
|
|
20141
20227
|
this.fsw._emitReady();
|
|
20142
20228
|
return true;
|
|
@@ -20165,9 +20251,9 @@ var NodeFsHandler = class {
|
|
|
20165
20251
|
return;
|
|
20166
20252
|
}
|
|
20167
20253
|
const item = entry.path;
|
|
20168
|
-
let
|
|
20254
|
+
let path4 = sysPath.join(directory, item);
|
|
20169
20255
|
current.add(item);
|
|
20170
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
20256
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path4, item)) {
|
|
20171
20257
|
return;
|
|
20172
20258
|
}
|
|
20173
20259
|
if (this.fsw.closed) {
|
|
@@ -20176,8 +20262,8 @@ var NodeFsHandler = class {
|
|
|
20176
20262
|
}
|
|
20177
20263
|
if (item === target || !target && !previous.has(item)) {
|
|
20178
20264
|
this.fsw._incrReadyCount();
|
|
20179
|
-
|
|
20180
|
-
this._addToNodeFs(
|
|
20265
|
+
path4 = sysPath.join(dir, sysPath.relative(dir, path4));
|
|
20266
|
+
this._addToNodeFs(path4, initialAdd, wh, depth + 1);
|
|
20181
20267
|
}
|
|
20182
20268
|
}).on(EV.ERROR, this._boundHandleError);
|
|
20183
20269
|
return new Promise((resolve3, reject) => {
|
|
@@ -20246,13 +20332,13 @@ var NodeFsHandler = class {
|
|
|
20246
20332
|
* @param depth Child path actually targeted for watch
|
|
20247
20333
|
* @param target Child path actually targeted for watch
|
|
20248
20334
|
*/
|
|
20249
|
-
async _addToNodeFs(
|
|
20335
|
+
async _addToNodeFs(path4, initialAdd, priorWh, depth, target) {
|
|
20250
20336
|
const ready = this.fsw._emitReady;
|
|
20251
|
-
if (this.fsw._isIgnored(
|
|
20337
|
+
if (this.fsw._isIgnored(path4) || this.fsw.closed) {
|
|
20252
20338
|
ready();
|
|
20253
20339
|
return false;
|
|
20254
20340
|
}
|
|
20255
|
-
const wh = this.fsw._getWatchHelpers(
|
|
20341
|
+
const wh = this.fsw._getWatchHelpers(path4);
|
|
20256
20342
|
if (priorWh) {
|
|
20257
20343
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
20258
20344
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -20268,8 +20354,8 @@ var NodeFsHandler = class {
|
|
|
20268
20354
|
const follow = this.fsw.options.followSymlinks;
|
|
20269
20355
|
let closer;
|
|
20270
20356
|
if (stats.isDirectory()) {
|
|
20271
|
-
const absPath = sysPath.resolve(
|
|
20272
|
-
const targetPath = follow ? await fsrealpath(
|
|
20357
|
+
const absPath = sysPath.resolve(path4);
|
|
20358
|
+
const targetPath = follow ? await fsrealpath(path4) : path4;
|
|
20273
20359
|
if (this.fsw.closed)
|
|
20274
20360
|
return;
|
|
20275
20361
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -20279,29 +20365,29 @@ var NodeFsHandler = class {
|
|
|
20279
20365
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
20280
20366
|
}
|
|
20281
20367
|
} else if (stats.isSymbolicLink()) {
|
|
20282
|
-
const targetPath = follow ? await fsrealpath(
|
|
20368
|
+
const targetPath = follow ? await fsrealpath(path4) : path4;
|
|
20283
20369
|
if (this.fsw.closed)
|
|
20284
20370
|
return;
|
|
20285
20371
|
const parent = sysPath.dirname(wh.watchPath);
|
|
20286
20372
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
20287
20373
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
20288
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
20374
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path4, wh, targetPath);
|
|
20289
20375
|
if (this.fsw.closed)
|
|
20290
20376
|
return;
|
|
20291
20377
|
if (targetPath !== void 0) {
|
|
20292
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
20378
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path4), targetPath);
|
|
20293
20379
|
}
|
|
20294
20380
|
} else {
|
|
20295
20381
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
20296
20382
|
}
|
|
20297
20383
|
ready();
|
|
20298
20384
|
if (closer)
|
|
20299
|
-
this.fsw._addPathCloser(
|
|
20385
|
+
this.fsw._addPathCloser(path4, closer);
|
|
20300
20386
|
return false;
|
|
20301
20387
|
} catch (error) {
|
|
20302
20388
|
if (this.fsw._handleError(error)) {
|
|
20303
20389
|
ready();
|
|
20304
|
-
return
|
|
20390
|
+
return path4;
|
|
20305
20391
|
}
|
|
20306
20392
|
}
|
|
20307
20393
|
}
|
|
@@ -20344,26 +20430,26 @@ function createPattern(matcher) {
|
|
|
20344
20430
|
}
|
|
20345
20431
|
return () => false;
|
|
20346
20432
|
}
|
|
20347
|
-
function normalizePath(
|
|
20348
|
-
if (typeof
|
|
20433
|
+
function normalizePath(path4) {
|
|
20434
|
+
if (typeof path4 !== "string")
|
|
20349
20435
|
throw new Error("string expected");
|
|
20350
|
-
|
|
20351
|
-
|
|
20436
|
+
path4 = sysPath2.normalize(path4);
|
|
20437
|
+
path4 = path4.replace(/\\/g, "/");
|
|
20352
20438
|
let prepend = false;
|
|
20353
|
-
if (
|
|
20439
|
+
if (path4.startsWith("//"))
|
|
20354
20440
|
prepend = true;
|
|
20355
20441
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
20356
|
-
while (
|
|
20357
|
-
|
|
20442
|
+
while (path4.match(DOUBLE_SLASH_RE2))
|
|
20443
|
+
path4 = path4.replace(DOUBLE_SLASH_RE2, "/");
|
|
20358
20444
|
if (prepend)
|
|
20359
|
-
|
|
20360
|
-
return
|
|
20445
|
+
path4 = "/" + path4;
|
|
20446
|
+
return path4;
|
|
20361
20447
|
}
|
|
20362
20448
|
function matchPatterns(patterns, testString, stats) {
|
|
20363
|
-
const
|
|
20449
|
+
const path4 = normalizePath(testString);
|
|
20364
20450
|
for (let index = 0; index < patterns.length; index++) {
|
|
20365
20451
|
const pattern = patterns[index];
|
|
20366
|
-
if (pattern(
|
|
20452
|
+
if (pattern(path4, stats)) {
|
|
20367
20453
|
return true;
|
|
20368
20454
|
}
|
|
20369
20455
|
}
|
|
@@ -20403,19 +20489,19 @@ var toUnix = (string) => {
|
|
|
20403
20489
|
}
|
|
20404
20490
|
return str;
|
|
20405
20491
|
};
|
|
20406
|
-
var normalizePathToUnix = (
|
|
20407
|
-
var normalizeIgnored = (cwd2 = "") => (
|
|
20408
|
-
if (typeof
|
|
20409
|
-
return normalizePathToUnix(sysPath2.isAbsolute(
|
|
20492
|
+
var normalizePathToUnix = (path4) => toUnix(sysPath2.normalize(toUnix(path4)));
|
|
20493
|
+
var normalizeIgnored = (cwd2 = "") => (path4) => {
|
|
20494
|
+
if (typeof path4 === "string") {
|
|
20495
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path4) ? path4 : sysPath2.join(cwd2, path4));
|
|
20410
20496
|
} else {
|
|
20411
|
-
return
|
|
20497
|
+
return path4;
|
|
20412
20498
|
}
|
|
20413
20499
|
};
|
|
20414
|
-
var getAbsolutePath = (
|
|
20415
|
-
if (sysPath2.isAbsolute(
|
|
20416
|
-
return
|
|
20500
|
+
var getAbsolutePath = (path4, cwd2) => {
|
|
20501
|
+
if (sysPath2.isAbsolute(path4)) {
|
|
20502
|
+
return path4;
|
|
20417
20503
|
}
|
|
20418
|
-
return sysPath2.join(cwd2,
|
|
20504
|
+
return sysPath2.join(cwd2, path4);
|
|
20419
20505
|
};
|
|
20420
20506
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
20421
20507
|
var DirEntry = class {
|
|
@@ -20470,10 +20556,10 @@ var DirEntry = class {
|
|
|
20470
20556
|
var STAT_METHOD_F = "stat";
|
|
20471
20557
|
var STAT_METHOD_L = "lstat";
|
|
20472
20558
|
var WatchHelper = class {
|
|
20473
|
-
constructor(
|
|
20559
|
+
constructor(path4, follow, fsw) {
|
|
20474
20560
|
this.fsw = fsw;
|
|
20475
|
-
const watchPath =
|
|
20476
|
-
this.path =
|
|
20561
|
+
const watchPath = path4;
|
|
20562
|
+
this.path = path4 = path4.replace(REPLACER_RE, "");
|
|
20477
20563
|
this.watchPath = watchPath;
|
|
20478
20564
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
20479
20565
|
this.dirParts = [];
|
|
@@ -20595,20 +20681,20 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20595
20681
|
this._closePromise = void 0;
|
|
20596
20682
|
let paths = unifyPaths(paths_);
|
|
20597
20683
|
if (cwd2) {
|
|
20598
|
-
paths = paths.map((
|
|
20599
|
-
const absPath = getAbsolutePath(
|
|
20684
|
+
paths = paths.map((path4) => {
|
|
20685
|
+
const absPath = getAbsolutePath(path4, cwd2);
|
|
20600
20686
|
return absPath;
|
|
20601
20687
|
});
|
|
20602
20688
|
}
|
|
20603
|
-
paths.forEach((
|
|
20604
|
-
this._removeIgnoredPath(
|
|
20689
|
+
paths.forEach((path4) => {
|
|
20690
|
+
this._removeIgnoredPath(path4);
|
|
20605
20691
|
});
|
|
20606
20692
|
this._userIgnored = void 0;
|
|
20607
20693
|
if (!this._readyCount)
|
|
20608
20694
|
this._readyCount = 0;
|
|
20609
20695
|
this._readyCount += paths.length;
|
|
20610
|
-
Promise.all(paths.map(async (
|
|
20611
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
20696
|
+
Promise.all(paths.map(async (path4) => {
|
|
20697
|
+
const res = await this._nodeFsHandler._addToNodeFs(path4, !_internal, void 0, 0, _origAdd);
|
|
20612
20698
|
if (res)
|
|
20613
20699
|
this._emitReady();
|
|
20614
20700
|
return res;
|
|
@@ -20630,17 +20716,17 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20630
20716
|
return this;
|
|
20631
20717
|
const paths = unifyPaths(paths_);
|
|
20632
20718
|
const { cwd: cwd2 } = this.options;
|
|
20633
|
-
paths.forEach((
|
|
20634
|
-
if (!sysPath2.isAbsolute(
|
|
20719
|
+
paths.forEach((path4) => {
|
|
20720
|
+
if (!sysPath2.isAbsolute(path4) && !this._closers.has(path4)) {
|
|
20635
20721
|
if (cwd2)
|
|
20636
|
-
|
|
20637
|
-
|
|
20722
|
+
path4 = sysPath2.join(cwd2, path4);
|
|
20723
|
+
path4 = sysPath2.resolve(path4);
|
|
20638
20724
|
}
|
|
20639
|
-
this._closePath(
|
|
20640
|
-
this._addIgnoredPath(
|
|
20641
|
-
if (this._watched.has(
|
|
20725
|
+
this._closePath(path4);
|
|
20726
|
+
this._addIgnoredPath(path4);
|
|
20727
|
+
if (this._watched.has(path4)) {
|
|
20642
20728
|
this._addIgnoredPath({
|
|
20643
|
-
path:
|
|
20729
|
+
path: path4,
|
|
20644
20730
|
recursive: true
|
|
20645
20731
|
});
|
|
20646
20732
|
}
|
|
@@ -20704,38 +20790,38 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20704
20790
|
* @param stats arguments to be passed with event
|
|
20705
20791
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
20706
20792
|
*/
|
|
20707
|
-
async _emit(event,
|
|
20793
|
+
async _emit(event, path4, stats) {
|
|
20708
20794
|
if (this.closed)
|
|
20709
20795
|
return;
|
|
20710
20796
|
const opts = this.options;
|
|
20711
20797
|
if (isWindows3)
|
|
20712
|
-
|
|
20798
|
+
path4 = sysPath2.normalize(path4);
|
|
20713
20799
|
if (opts.cwd)
|
|
20714
|
-
|
|
20715
|
-
const args = [
|
|
20800
|
+
path4 = sysPath2.relative(opts.cwd, path4);
|
|
20801
|
+
const args = [path4];
|
|
20716
20802
|
if (stats != null)
|
|
20717
20803
|
args.push(stats);
|
|
20718
20804
|
const awf = opts.awaitWriteFinish;
|
|
20719
20805
|
let pw;
|
|
20720
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
20806
|
+
if (awf && (pw = this._pendingWrites.get(path4))) {
|
|
20721
20807
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
20722
20808
|
return this;
|
|
20723
20809
|
}
|
|
20724
20810
|
if (opts.atomic) {
|
|
20725
20811
|
if (event === EVENTS.UNLINK) {
|
|
20726
|
-
this._pendingUnlinks.set(
|
|
20812
|
+
this._pendingUnlinks.set(path4, [event, ...args]);
|
|
20727
20813
|
setTimeout(() => {
|
|
20728
|
-
this._pendingUnlinks.forEach((entry,
|
|
20814
|
+
this._pendingUnlinks.forEach((entry, path5) => {
|
|
20729
20815
|
this.emit(...entry);
|
|
20730
20816
|
this.emit(EVENTS.ALL, ...entry);
|
|
20731
|
-
this._pendingUnlinks.delete(
|
|
20817
|
+
this._pendingUnlinks.delete(path5);
|
|
20732
20818
|
});
|
|
20733
20819
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
20734
20820
|
return this;
|
|
20735
20821
|
}
|
|
20736
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
20822
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path4)) {
|
|
20737
20823
|
event = EVENTS.CHANGE;
|
|
20738
|
-
this._pendingUnlinks.delete(
|
|
20824
|
+
this._pendingUnlinks.delete(path4);
|
|
20739
20825
|
}
|
|
20740
20826
|
}
|
|
20741
20827
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -20753,16 +20839,16 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20753
20839
|
this.emitWithAll(event, args);
|
|
20754
20840
|
}
|
|
20755
20841
|
};
|
|
20756
|
-
this._awaitWriteFinish(
|
|
20842
|
+
this._awaitWriteFinish(path4, awf.stabilityThreshold, event, awfEmit);
|
|
20757
20843
|
return this;
|
|
20758
20844
|
}
|
|
20759
20845
|
if (event === EVENTS.CHANGE) {
|
|
20760
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
20846
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path4, 50);
|
|
20761
20847
|
if (isThrottled)
|
|
20762
20848
|
return this;
|
|
20763
20849
|
}
|
|
20764
20850
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
20765
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
20851
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path4) : path4;
|
|
20766
20852
|
let stats2;
|
|
20767
20853
|
try {
|
|
20768
20854
|
stats2 = await stat3(fullPath);
|
|
@@ -20793,23 +20879,23 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20793
20879
|
* @param timeout duration of time to suppress duplicate actions
|
|
20794
20880
|
* @returns tracking object or false if action should be suppressed
|
|
20795
20881
|
*/
|
|
20796
|
-
_throttle(actionType,
|
|
20882
|
+
_throttle(actionType, path4, timeout) {
|
|
20797
20883
|
if (!this._throttled.has(actionType)) {
|
|
20798
20884
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
20799
20885
|
}
|
|
20800
20886
|
const action = this._throttled.get(actionType);
|
|
20801
20887
|
if (!action)
|
|
20802
20888
|
throw new Error("invalid throttle");
|
|
20803
|
-
const actionPath = action.get(
|
|
20889
|
+
const actionPath = action.get(path4);
|
|
20804
20890
|
if (actionPath) {
|
|
20805
20891
|
actionPath.count++;
|
|
20806
20892
|
return false;
|
|
20807
20893
|
}
|
|
20808
20894
|
let timeoutObject;
|
|
20809
20895
|
const clear = () => {
|
|
20810
|
-
const item = action.get(
|
|
20896
|
+
const item = action.get(path4);
|
|
20811
20897
|
const count = item ? item.count : 0;
|
|
20812
|
-
action.delete(
|
|
20898
|
+
action.delete(path4);
|
|
20813
20899
|
clearTimeout(timeoutObject);
|
|
20814
20900
|
if (item)
|
|
20815
20901
|
clearTimeout(item.timeoutObject);
|
|
@@ -20817,7 +20903,7 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20817
20903
|
};
|
|
20818
20904
|
timeoutObject = setTimeout(clear, timeout);
|
|
20819
20905
|
const thr = { timeoutObject, clear, count: 0 };
|
|
20820
|
-
action.set(
|
|
20906
|
+
action.set(path4, thr);
|
|
20821
20907
|
return thr;
|
|
20822
20908
|
}
|
|
20823
20909
|
_incrReadyCount() {
|
|
@@ -20831,44 +20917,44 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20831
20917
|
* @param event
|
|
20832
20918
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
20833
20919
|
*/
|
|
20834
|
-
_awaitWriteFinish(
|
|
20920
|
+
_awaitWriteFinish(path4, threshold, event, awfEmit) {
|
|
20835
20921
|
const awf = this.options.awaitWriteFinish;
|
|
20836
20922
|
if (typeof awf !== "object")
|
|
20837
20923
|
return;
|
|
20838
20924
|
const pollInterval = awf.pollInterval;
|
|
20839
20925
|
let timeoutHandler;
|
|
20840
|
-
let fullPath =
|
|
20841
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
20842
|
-
fullPath = sysPath2.join(this.options.cwd,
|
|
20926
|
+
let fullPath = path4;
|
|
20927
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path4)) {
|
|
20928
|
+
fullPath = sysPath2.join(this.options.cwd, path4);
|
|
20843
20929
|
}
|
|
20844
20930
|
const now = /* @__PURE__ */ new Date();
|
|
20845
20931
|
const writes = this._pendingWrites;
|
|
20846
20932
|
function awaitWriteFinishFn(prevStat) {
|
|
20847
20933
|
statcb(fullPath, (err, curStat) => {
|
|
20848
|
-
if (err || !writes.has(
|
|
20934
|
+
if (err || !writes.has(path4)) {
|
|
20849
20935
|
if (err && err.code !== "ENOENT")
|
|
20850
20936
|
awfEmit(err);
|
|
20851
20937
|
return;
|
|
20852
20938
|
}
|
|
20853
20939
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
20854
20940
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
20855
|
-
writes.get(
|
|
20941
|
+
writes.get(path4).lastChange = now2;
|
|
20856
20942
|
}
|
|
20857
|
-
const pw = writes.get(
|
|
20943
|
+
const pw = writes.get(path4);
|
|
20858
20944
|
const df = now2 - pw.lastChange;
|
|
20859
20945
|
if (df >= threshold) {
|
|
20860
|
-
writes.delete(
|
|
20946
|
+
writes.delete(path4);
|
|
20861
20947
|
awfEmit(void 0, curStat);
|
|
20862
20948
|
} else {
|
|
20863
20949
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
20864
20950
|
}
|
|
20865
20951
|
});
|
|
20866
20952
|
}
|
|
20867
|
-
if (!writes.has(
|
|
20868
|
-
writes.set(
|
|
20953
|
+
if (!writes.has(path4)) {
|
|
20954
|
+
writes.set(path4, {
|
|
20869
20955
|
lastChange: now,
|
|
20870
20956
|
cancelWait: () => {
|
|
20871
|
-
writes.delete(
|
|
20957
|
+
writes.delete(path4);
|
|
20872
20958
|
clearTimeout(timeoutHandler);
|
|
20873
20959
|
return event;
|
|
20874
20960
|
}
|
|
@@ -20879,8 +20965,8 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20879
20965
|
/**
|
|
20880
20966
|
* Determines whether user has asked to ignore this path.
|
|
20881
20967
|
*/
|
|
20882
|
-
_isIgnored(
|
|
20883
|
-
if (this.options.atomic && DOT_RE.test(
|
|
20968
|
+
_isIgnored(path4, stats) {
|
|
20969
|
+
if (this.options.atomic && DOT_RE.test(path4))
|
|
20884
20970
|
return true;
|
|
20885
20971
|
if (!this._userIgnored) {
|
|
20886
20972
|
const { cwd: cwd2 } = this.options;
|
|
@@ -20890,17 +20976,17 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20890
20976
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd2)), ...ignored];
|
|
20891
20977
|
this._userIgnored = anymatch(list, void 0);
|
|
20892
20978
|
}
|
|
20893
|
-
return this._userIgnored(
|
|
20979
|
+
return this._userIgnored(path4, stats);
|
|
20894
20980
|
}
|
|
20895
|
-
_isntIgnored(
|
|
20896
|
-
return !this._isIgnored(
|
|
20981
|
+
_isntIgnored(path4, stat4) {
|
|
20982
|
+
return !this._isIgnored(path4, stat4);
|
|
20897
20983
|
}
|
|
20898
20984
|
/**
|
|
20899
20985
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
20900
20986
|
* @param path file or directory pattern being watched
|
|
20901
20987
|
*/
|
|
20902
|
-
_getWatchHelpers(
|
|
20903
|
-
return new WatchHelper(
|
|
20988
|
+
_getWatchHelpers(path4) {
|
|
20989
|
+
return new WatchHelper(path4, this.options.followSymlinks, this);
|
|
20904
20990
|
}
|
|
20905
20991
|
// Directory helpers
|
|
20906
20992
|
// -----------------
|
|
@@ -20932,63 +21018,63 @@ var FSWatcher = class extends EventEmitter3 {
|
|
|
20932
21018
|
* @param item base path of item/directory
|
|
20933
21019
|
*/
|
|
20934
21020
|
_remove(directory, item, isDirectory) {
|
|
20935
|
-
const
|
|
20936
|
-
const fullPath = sysPath2.resolve(
|
|
20937
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
20938
|
-
if (!this._throttle("remove",
|
|
21021
|
+
const path4 = sysPath2.join(directory, item);
|
|
21022
|
+
const fullPath = sysPath2.resolve(path4);
|
|
21023
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path4) || this._watched.has(fullPath);
|
|
21024
|
+
if (!this._throttle("remove", path4, 100))
|
|
20939
21025
|
return;
|
|
20940
21026
|
if (!isDirectory && this._watched.size === 1) {
|
|
20941
21027
|
this.add(directory, item, true);
|
|
20942
21028
|
}
|
|
20943
|
-
const wp = this._getWatchedDir(
|
|
21029
|
+
const wp = this._getWatchedDir(path4);
|
|
20944
21030
|
const nestedDirectoryChildren = wp.getChildren();
|
|
20945
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
21031
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path4, nested));
|
|
20946
21032
|
const parent = this._getWatchedDir(directory);
|
|
20947
21033
|
const wasTracked = parent.has(item);
|
|
20948
21034
|
parent.remove(item);
|
|
20949
21035
|
if (this._symlinkPaths.has(fullPath)) {
|
|
20950
21036
|
this._symlinkPaths.delete(fullPath);
|
|
20951
21037
|
}
|
|
20952
|
-
let relPath =
|
|
21038
|
+
let relPath = path4;
|
|
20953
21039
|
if (this.options.cwd)
|
|
20954
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
21040
|
+
relPath = sysPath2.relative(this.options.cwd, path4);
|
|
20955
21041
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
20956
21042
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
20957
21043
|
if (event === EVENTS.ADD)
|
|
20958
21044
|
return;
|
|
20959
21045
|
}
|
|
20960
|
-
this._watched.delete(
|
|
21046
|
+
this._watched.delete(path4);
|
|
20961
21047
|
this._watched.delete(fullPath);
|
|
20962
21048
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
20963
|
-
if (wasTracked && !this._isIgnored(
|
|
20964
|
-
this._emit(eventName,
|
|
20965
|
-
this._closePath(
|
|
21049
|
+
if (wasTracked && !this._isIgnored(path4))
|
|
21050
|
+
this._emit(eventName, path4);
|
|
21051
|
+
this._closePath(path4);
|
|
20966
21052
|
}
|
|
20967
21053
|
/**
|
|
20968
21054
|
* Closes all watchers for a path
|
|
20969
21055
|
*/
|
|
20970
|
-
_closePath(
|
|
20971
|
-
this._closeFile(
|
|
20972
|
-
const dir = sysPath2.dirname(
|
|
20973
|
-
this._getWatchedDir(dir).remove(sysPath2.basename(
|
|
21056
|
+
_closePath(path4) {
|
|
21057
|
+
this._closeFile(path4);
|
|
21058
|
+
const dir = sysPath2.dirname(path4);
|
|
21059
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path4));
|
|
20974
21060
|
}
|
|
20975
21061
|
/**
|
|
20976
21062
|
* Closes only file-specific watchers
|
|
20977
21063
|
*/
|
|
20978
|
-
_closeFile(
|
|
20979
|
-
const closers = this._closers.get(
|
|
21064
|
+
_closeFile(path4) {
|
|
21065
|
+
const closers = this._closers.get(path4);
|
|
20980
21066
|
if (!closers)
|
|
20981
21067
|
return;
|
|
20982
21068
|
closers.forEach((closer) => closer());
|
|
20983
|
-
this._closers.delete(
|
|
21069
|
+
this._closers.delete(path4);
|
|
20984
21070
|
}
|
|
20985
|
-
_addPathCloser(
|
|
21071
|
+
_addPathCloser(path4, closer) {
|
|
20986
21072
|
if (!closer)
|
|
20987
21073
|
return;
|
|
20988
|
-
let list = this._closers.get(
|
|
21074
|
+
let list = this._closers.get(path4);
|
|
20989
21075
|
if (!list) {
|
|
20990
21076
|
list = [];
|
|
20991
|
-
this._closers.set(
|
|
21077
|
+
this._closers.set(path4, list);
|
|
20992
21078
|
}
|
|
20993
21079
|
list.push(closer);
|
|
20994
21080
|
}
|
|
@@ -21017,313 +21103,66 @@ function watch(paths, options = {}) {
|
|
|
21017
21103
|
}
|
|
21018
21104
|
|
|
21019
21105
|
// src/tui/hooks/useBrainfileLoader.ts
|
|
21020
|
-
var
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
|
|
21106
|
+
var import_core4 = __toESM(require_dist());
|
|
21107
|
+
|
|
21108
|
+
// src/tui/actions.ts
|
|
21109
|
+
var import_core3 = __toESM(require_dist());
|
|
21110
|
+
import * as fs3 from "fs";
|
|
21111
|
+
import * as path from "path";
|
|
21112
|
+
import { spawnSync } from "child_process";
|
|
21113
|
+
function readBrainfile(filePath) {
|
|
21024
21114
|
try {
|
|
21025
|
-
const
|
|
21026
|
-
|
|
21027
|
-
|
|
21028
|
-
|
|
21029
|
-
const stat4 = fs3.statSync(fullPath);
|
|
21030
|
-
parts.push(`board:${name}:${stat4.mtimeMs}:${stat4.size}`);
|
|
21031
|
-
} catch {
|
|
21032
|
-
}
|
|
21115
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
21116
|
+
const result = import_core3.Brainfile.parseWithErrors(content);
|
|
21117
|
+
if (!result.board) {
|
|
21118
|
+
return { board: null, content, error: result.error || "Failed to parse brainfile" };
|
|
21033
21119
|
}
|
|
21034
|
-
|
|
21120
|
+
return { board: result.board, content };
|
|
21121
|
+
} catch (err) {
|
|
21122
|
+
return { board: null, content: "", error: err instanceof Error ? err.message : String(err) };
|
|
21035
21123
|
}
|
|
21124
|
+
}
|
|
21125
|
+
function writeBrainfile(filePath, board) {
|
|
21036
21126
|
try {
|
|
21037
|
-
const
|
|
21038
|
-
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
parts.push(`logs:${name}:${stat4.mtimeMs}:${stat4.size}`);
|
|
21043
|
-
} catch {
|
|
21044
|
-
}
|
|
21045
|
-
}
|
|
21046
|
-
} catch {
|
|
21127
|
+
const content = import_core3.Brainfile.serialize(board);
|
|
21128
|
+
fs3.writeFileSync(filePath, content, "utf-8");
|
|
21129
|
+
return { success: true };
|
|
21130
|
+
} catch (err) {
|
|
21131
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
21047
21132
|
}
|
|
21048
|
-
return parts.join("|");
|
|
21049
21133
|
}
|
|
21050
|
-
function
|
|
21051
|
-
|
|
21052
|
-
const loadBrainfile = (0, import_react34.useCallback)((forceRefresh = false) => {
|
|
21134
|
+
function readBoardState(filePath) {
|
|
21135
|
+
if ((0, import_core.isV2)(filePath)) {
|
|
21053
21136
|
try {
|
|
21054
21137
|
const content = fs3.readFileSync(filePath, "utf-8");
|
|
21055
|
-
const
|
|
21056
|
-
|
|
21057
|
-
let parseError;
|
|
21058
|
-
if (v2) {
|
|
21059
|
-
try {
|
|
21060
|
-
board = (0, import_core.buildBoardFromV2)(filePath);
|
|
21061
|
-
} catch (err) {
|
|
21062
|
-
parseError = err instanceof Error ? err.message : String(err);
|
|
21063
|
-
}
|
|
21064
|
-
} else {
|
|
21065
|
-
const result = import_core2.Brainfile.parseWithErrors(content);
|
|
21066
|
-
if (result.board) {
|
|
21067
|
-
board = result.board;
|
|
21068
|
-
} else {
|
|
21069
|
-
parseError = result.error || "Not a valid brainfile";
|
|
21070
|
-
}
|
|
21071
|
-
}
|
|
21072
|
-
const contentHash = v2 ? (0, import_core2.hashBoardContent)(`${content}
|
|
21073
|
-
${getV2ChangeSignature(filePath)}`) : (0, import_core2.hashBoardContent)(content);
|
|
21074
|
-
setState((prev) => {
|
|
21075
|
-
if (!forceRefresh && prev.lastContentHash === contentHash) {
|
|
21076
|
-
return prev;
|
|
21077
|
-
}
|
|
21078
|
-
if (board) {
|
|
21079
|
-
const prevColumn = prev.board?.columns[prev.activeColumnIndex];
|
|
21080
|
-
const prevTaskId = prevColumn?.tasks[prev.selectedTaskIndex]?.id;
|
|
21081
|
-
let newColumnIndex = prev.activeColumnIndex;
|
|
21082
|
-
let newTaskIndex = 0;
|
|
21083
|
-
if (prevColumn) {
|
|
21084
|
-
const colIdx = board.columns.findIndex((c) => c.id === prevColumn.id);
|
|
21085
|
-
if (colIdx >= 0) {
|
|
21086
|
-
newColumnIndex = colIdx;
|
|
21087
|
-
if (prevTaskId) {
|
|
21088
|
-
const taskIdx = board.columns[colIdx].tasks.findIndex((t) => t.id === prevTaskId);
|
|
21089
|
-
if (taskIdx >= 0) newTaskIndex = taskIdx;
|
|
21090
|
-
}
|
|
21091
|
-
}
|
|
21092
|
-
}
|
|
21093
|
-
newColumnIndex = Math.min(newColumnIndex, Math.max(0, board.columns.length - 1));
|
|
21094
|
-
const col = board.columns[newColumnIndex];
|
|
21095
|
-
newTaskIndex = Math.min(newTaskIndex, Math.max(0, (col?.tasks.length || 1) - 1));
|
|
21096
|
-
return {
|
|
21097
|
-
...prev,
|
|
21098
|
-
board,
|
|
21099
|
-
error: null,
|
|
21100
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
21101
|
-
activeColumnIndex: newColumnIndex,
|
|
21102
|
-
selectedTaskIndex: newTaskIndex,
|
|
21103
|
-
reloadFlash: true,
|
|
21104
|
-
lastContentHash: contentHash
|
|
21105
|
-
};
|
|
21106
|
-
}
|
|
21107
|
-
let errorMessage;
|
|
21108
|
-
if (parseError) {
|
|
21109
|
-
errorMessage = parseError;
|
|
21110
|
-
} else {
|
|
21111
|
-
errorMessage = "Not a valid brainfile";
|
|
21112
|
-
}
|
|
21113
|
-
return {
|
|
21114
|
-
...prev,
|
|
21115
|
-
error: errorMessage,
|
|
21116
|
-
lastContentHash: contentHash
|
|
21117
|
-
};
|
|
21118
|
-
});
|
|
21119
|
-
if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
|
|
21120
|
-
flashTimeoutRef.current = setTimeout(() => {
|
|
21121
|
-
setState((prev) => ({ ...prev, reloadFlash: false }));
|
|
21122
|
-
}, 1e3);
|
|
21138
|
+
const board = (0, import_core.buildBoardFromV2)(filePath);
|
|
21139
|
+
return { board, content };
|
|
21123
21140
|
} catch (err) {
|
|
21124
|
-
|
|
21125
|
-
let userMessage = errorMsg;
|
|
21126
|
-
if (errorMsg.includes("ENOENT")) {
|
|
21127
|
-
userMessage = `File not found: ${filePath}`;
|
|
21128
|
-
} else if (errorMsg.includes("EACCES")) {
|
|
21129
|
-
userMessage = `Permission denied: ${filePath}`;
|
|
21130
|
-
} else if (errorMsg.includes("EISDIR")) {
|
|
21131
|
-
userMessage = `Path is a directory: ${filePath}`;
|
|
21132
|
-
}
|
|
21133
|
-
setState((prev) => ({
|
|
21134
|
-
...prev,
|
|
21135
|
-
error: userMessage
|
|
21136
|
-
}));
|
|
21141
|
+
return { board: null, content: "", error: err instanceof Error ? err.message : String(err) };
|
|
21137
21142
|
}
|
|
21138
|
-
}
|
|
21139
|
-
|
|
21140
|
-
loadBrainfile();
|
|
21141
|
-
}, [loadBrainfile]);
|
|
21142
|
-
(0, import_react34.useEffect)(() => {
|
|
21143
|
-
return () => {
|
|
21144
|
-
if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
|
|
21145
|
-
};
|
|
21146
|
-
}, []);
|
|
21147
|
-
(0, import_react34.useEffect)(() => {
|
|
21148
|
-
const v2 = (0, import_core.isV2)(filePath);
|
|
21149
|
-
const dirs = v2 ? (0, import_core.getV2Dirs)(filePath) : null;
|
|
21150
|
-
const watchPaths = v2 && dirs ? [filePath, dirs.boardDir, dirs.logsDir] : [filePath];
|
|
21151
|
-
const watcher = watch(watchPaths, {
|
|
21152
|
-
persistent: true,
|
|
21153
|
-
ignoreInitial: true,
|
|
21154
|
-
usePolling: true,
|
|
21155
|
-
interval: 750,
|
|
21156
|
-
awaitWriteFinish: {
|
|
21157
|
-
stabilityThreshold: 250,
|
|
21158
|
-
pollInterval: 100
|
|
21159
|
-
},
|
|
21160
|
-
depth: v2 ? 1 : 0
|
|
21161
|
-
});
|
|
21162
|
-
watcher.on("add", () => loadBrainfile());
|
|
21163
|
-
watcher.on("change", () => loadBrainfile());
|
|
21164
|
-
watcher.on("unlink", () => loadBrainfile());
|
|
21165
|
-
watcher.on("error", (err) => {
|
|
21166
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
21167
|
-
setState((prev) => ({
|
|
21168
|
-
...prev,
|
|
21169
|
-
error: `File watcher error: ${message}`
|
|
21170
|
-
}));
|
|
21171
|
-
});
|
|
21172
|
-
return () => {
|
|
21173
|
-
watcher.close();
|
|
21174
|
-
};
|
|
21175
|
-
}, [filePath, loadBrainfile, setState]);
|
|
21176
|
-
return { loadBrainfile };
|
|
21177
|
-
}
|
|
21178
|
-
|
|
21179
|
-
// src/tui/hooks/useKeyboardNavigation.ts
|
|
21180
|
-
var import_react35 = __toESM(require_react());
|
|
21181
|
-
|
|
21182
|
-
// src/tui/utils.ts
|
|
21183
|
-
var import_core3 = __toESM(require_dist());
|
|
21184
|
-
|
|
21185
|
-
// src/tui/text.ts
|
|
21186
|
-
function truncate(text, maxWidth) {
|
|
21187
|
-
if (!text) return " ";
|
|
21188
|
-
if (maxWidth <= 0) return " ";
|
|
21189
|
-
if (maxWidth <= 3) return text.slice(0, maxWidth) || " ";
|
|
21190
|
-
if (text.length <= maxWidth) return text;
|
|
21191
|
-
return `${text.slice(0, maxWidth - 1)}\u2026`;
|
|
21192
|
-
}
|
|
21193
|
-
function wrapText2(text, maxWidth) {
|
|
21194
|
-
if (!text || maxWidth <= 0) return [];
|
|
21195
|
-
const words = text.split(/\s+/);
|
|
21196
|
-
const lines = [];
|
|
21197
|
-
let currentLine = "";
|
|
21198
|
-
for (const word of words) {
|
|
21199
|
-
if (word.length > maxWidth) {
|
|
21200
|
-
if (currentLine) {
|
|
21201
|
-
lines.push(currentLine);
|
|
21202
|
-
currentLine = "";
|
|
21203
|
-
}
|
|
21204
|
-
for (let i = 0; i < word.length; i += maxWidth) {
|
|
21205
|
-
lines.push(word.slice(i, i + maxWidth));
|
|
21206
|
-
}
|
|
21207
|
-
} else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
21208
|
-
currentLine = currentLine ? `${currentLine} ${word}` : word;
|
|
21209
|
-
} else {
|
|
21210
|
-
if (currentLine) lines.push(currentLine);
|
|
21211
|
-
currentLine = word;
|
|
21212
|
-
}
|
|
21213
|
-
}
|
|
21214
|
-
if (currentLine) lines.push(currentLine);
|
|
21215
|
-
return lines.length > 0 ? lines : [""];
|
|
21216
|
-
}
|
|
21217
|
-
function pad(n) {
|
|
21218
|
-
return " ".repeat(Math.max(0, n));
|
|
21219
|
-
}
|
|
21220
|
-
|
|
21221
|
-
// src/tui/utils.ts
|
|
21222
|
-
function getPriorityColor(priority) {
|
|
21223
|
-
switch (priority?.toLowerCase()) {
|
|
21224
|
-
case "critical":
|
|
21225
|
-
return PALETTE.critical;
|
|
21226
|
-
case "giga":
|
|
21227
|
-
return PALETTE.giga;
|
|
21228
|
-
case "high":
|
|
21229
|
-
return PALETTE.high;
|
|
21230
|
-
case "medium":
|
|
21231
|
-
return PALETTE.medium;
|
|
21232
|
-
case "low":
|
|
21233
|
-
return PALETTE.low;
|
|
21234
|
-
default:
|
|
21235
|
-
return PALETTE.textDim;
|
|
21236
|
-
}
|
|
21237
|
-
}
|
|
21238
|
-
function getDocType(task) {
|
|
21239
|
-
return (task.type || "task").toLowerCase();
|
|
21240
|
-
}
|
|
21241
|
-
function isCompletable(task) {
|
|
21242
|
-
if (!task) return false;
|
|
21243
|
-
return getDocType(task) !== "adr";
|
|
21244
|
-
}
|
|
21245
|
-
function getSubtaskProgress(task) {
|
|
21246
|
-
const subtasks = task.subtasks ?? [];
|
|
21247
|
-
if (subtasks.length === 0) return void 0;
|
|
21248
|
-
const done = subtasks.filter((s) => s.completed).length;
|
|
21249
|
-
return `${done}/${subtasks.length}`;
|
|
21250
|
-
}
|
|
21251
|
-
function getContractState(task) {
|
|
21252
|
-
const contract = task.contract;
|
|
21253
|
-
return contract?.status;
|
|
21254
|
-
}
|
|
21255
|
-
|
|
21256
|
-
// src/tui/actions.ts
|
|
21257
|
-
var import_core4 = __toESM(require_dist());
|
|
21258
|
-
import * as fs4 from "fs";
|
|
21259
|
-
import * as path2 from "path";
|
|
21260
|
-
import * as os3 from "os";
|
|
21261
|
-
import { spawnSync } from "child_process";
|
|
21262
|
-
function readBrainfile(filePath) {
|
|
21263
|
-
try {
|
|
21264
|
-
const content = fs4.readFileSync(filePath, "utf-8");
|
|
21265
|
-
const result = import_core4.Brainfile.parseWithErrors(content);
|
|
21266
|
-
if (!result.board) {
|
|
21267
|
-
return { board: null, content, error: result.error || "Failed to parse brainfile" };
|
|
21268
|
-
}
|
|
21269
|
-
return { board: result.board, content };
|
|
21270
|
-
} catch (err) {
|
|
21271
|
-
return { board: null, content: "", error: err instanceof Error ? err.message : String(err) };
|
|
21272
|
-
}
|
|
21273
|
-
}
|
|
21274
|
-
function writeBrainfile(filePath, board) {
|
|
21275
|
-
try {
|
|
21276
|
-
const content = import_core4.Brainfile.serialize(board);
|
|
21277
|
-
fs4.writeFileSync(filePath, content, "utf-8");
|
|
21278
|
-
return { success: true };
|
|
21279
|
-
} catch (err) {
|
|
21280
|
-
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
21281
|
-
}
|
|
21282
|
-
}
|
|
21283
|
-
function readBoardState(filePath) {
|
|
21284
|
-
if ((0, import_core.isV2)(filePath)) {
|
|
21285
|
-
try {
|
|
21286
|
-
const content = fs4.readFileSync(filePath, "utf-8");
|
|
21287
|
-
const board = (0, import_core.buildBoardFromV2)(filePath);
|
|
21288
|
-
return { board, content };
|
|
21289
|
-
} catch (err) {
|
|
21290
|
-
return { board: null, content: "", error: err instanceof Error ? err.message : String(err) };
|
|
21291
|
-
}
|
|
21292
|
-
}
|
|
21293
|
-
return readBrainfile(filePath);
|
|
21294
|
-
}
|
|
21295
|
-
function readBoardConfigOnly(filePath) {
|
|
21296
|
-
if ((0, import_core.isV2)(filePath)) {
|
|
21297
|
-
try {
|
|
21298
|
-
const content = fs4.readFileSync(filePath, "utf-8");
|
|
21299
|
-
const board = (0, import_core.readV2BoardConfig)(filePath);
|
|
21300
|
-
return { board, content };
|
|
21301
|
-
} catch (err) {
|
|
21302
|
-
return { board: null, content: "", error: err instanceof Error ? err.message : String(err) };
|
|
21303
|
-
}
|
|
21304
|
-
}
|
|
21305
|
-
return readBrainfile(filePath);
|
|
21143
|
+
}
|
|
21144
|
+
return readBrainfile(filePath);
|
|
21306
21145
|
}
|
|
21307
21146
|
function resolveV2TaskPath(filePath, taskId) {
|
|
21308
21147
|
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
21309
|
-
const doc = (0,
|
|
21148
|
+
const doc = (0, import_core3.findTask)(dirs.boardDir, taskId);
|
|
21310
21149
|
if (!doc || !doc.filePath) return null;
|
|
21311
21150
|
return { taskPath: doc.filePath, task: doc.task };
|
|
21312
21151
|
}
|
|
21313
21152
|
function moveTaskAction(filePath, taskId, targetColumnId) {
|
|
21314
21153
|
const { board, error } = readBoardState(filePath);
|
|
21315
21154
|
if (!board) return { success: false, error };
|
|
21316
|
-
const taskInfo = (0,
|
|
21155
|
+
const taskInfo = (0, import_core3.findTaskById)(board, taskId);
|
|
21317
21156
|
if (!taskInfo) return { success: false, error: `Task ${taskId} not found` };
|
|
21318
|
-
const targetColumn = (0,
|
|
21157
|
+
const targetColumn = (0, import_core3.findColumnById)(board, targetColumnId) || (0, import_core3.findColumnByName)(board, targetColumnId);
|
|
21319
21158
|
if (!targetColumn) return { success: false, error: `Column ${targetColumnId} not found` };
|
|
21320
21159
|
if (taskInfo.column.id === targetColumn.id) {
|
|
21321
21160
|
return { success: true, message: "Task already in this column" };
|
|
21322
21161
|
}
|
|
21323
21162
|
if ((0, import_core.isV2)(filePath)) {
|
|
21324
|
-
const boardFile = (0,
|
|
21163
|
+
const boardFile = (0, import_core3.readBoardConfig)(filePath);
|
|
21325
21164
|
if (!boardFile) return { success: false, error: `Failed to parse brainfile: ${filePath}` };
|
|
21326
|
-
const moveResult = (0,
|
|
21165
|
+
const moveResult = (0, import_core3.moveTaskFileToColumn)(
|
|
21327
21166
|
(0, import_core.getV2Dirs)(filePath),
|
|
21328
21167
|
boardFile.config,
|
|
21329
21168
|
taskId,
|
|
@@ -21335,7 +21174,7 @@ function moveTaskAction(filePath, taskId, targetColumnId) {
|
|
|
21335
21174
|
message: moveResult.autoCompleted ? `Completed via ${targetColumn.title}` : `Moved to ${targetColumn.title}`
|
|
21336
21175
|
};
|
|
21337
21176
|
}
|
|
21338
|
-
const result = (0,
|
|
21177
|
+
const result = (0, import_core3.moveTask)(board, taskId, taskInfo.column.id, targetColumn.id, targetColumn.tasks.length);
|
|
21339
21178
|
if (!result.success) return { success: false, error: result.error };
|
|
21340
21179
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21341
21180
|
if (!writeResult.success) return writeResult;
|
|
@@ -21344,26 +21183,26 @@ function moveTaskAction(filePath, taskId, targetColumnId) {
|
|
|
21344
21183
|
function deleteTaskAction(filePath, taskId) {
|
|
21345
21184
|
const { board, error } = readBoardState(filePath);
|
|
21346
21185
|
if (!board) return { success: false, error };
|
|
21347
|
-
const taskInfo = (0,
|
|
21186
|
+
const taskInfo = (0, import_core3.findTaskById)(board, taskId);
|
|
21348
21187
|
if (!taskInfo) return { success: false, error: `Task ${taskId} not found` };
|
|
21349
21188
|
if ((0, import_core.isV2)(filePath)) {
|
|
21350
21189
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21351
21190
|
if (!located) return { success: false, error: `Task ${taskId} not found` };
|
|
21352
|
-
const result2 = (0,
|
|
21191
|
+
const result2 = (0, import_core3.deleteTaskFile)(located.taskPath);
|
|
21353
21192
|
if (!result2.success) return { success: false, error: result2.error };
|
|
21354
21193
|
return { success: true, message: `Deleted ${taskId}` };
|
|
21355
21194
|
}
|
|
21356
|
-
const result = (0,
|
|
21195
|
+
const result = (0, import_core3.deleteTask)(board, taskInfo.column.id, taskId);
|
|
21357
21196
|
if (!result.success) return { success: false, error: result.error };
|
|
21358
21197
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21359
21198
|
if (!writeResult.success) return writeResult;
|
|
21360
21199
|
return { success: true, message: `Deleted ${taskId}` };
|
|
21361
21200
|
}
|
|
21362
21201
|
function getArchivePath(filePath) {
|
|
21363
|
-
const dir =
|
|
21364
|
-
const filename =
|
|
21202
|
+
const dir = path.dirname(filePath);
|
|
21203
|
+
const filename = path.basename(filePath);
|
|
21365
21204
|
const archiveFilename = filename.replace(/\.md$/, "-archive.md");
|
|
21366
|
-
return
|
|
21205
|
+
return path.join(dir, archiveFilename);
|
|
21367
21206
|
}
|
|
21368
21207
|
function createEmptyArchiveBoard() {
|
|
21369
21208
|
return {
|
|
@@ -21377,7 +21216,7 @@ function archiveTaskAction(filePath, taskId, options) {
|
|
|
21377
21216
|
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
21378
21217
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21379
21218
|
if (!located) return { success: false, error: `Task ${taskId} not found` };
|
|
21380
|
-
const result2 = (0,
|
|
21219
|
+
const result2 = (0, import_core3.completeTaskFile)(located.taskPath, dirs.logsDir, { force: options?.force });
|
|
21381
21220
|
if (!result2.success) {
|
|
21382
21221
|
return {
|
|
21383
21222
|
success: false,
|
|
@@ -21389,20 +21228,20 @@ function archiveTaskAction(filePath, taskId, options) {
|
|
|
21389
21228
|
}
|
|
21390
21229
|
const { board, error } = readBrainfile(filePath);
|
|
21391
21230
|
if (!board) return { success: false, error };
|
|
21392
|
-
const taskInfo = (0,
|
|
21231
|
+
const taskInfo = (0, import_core3.findTaskById)(board, taskId);
|
|
21393
21232
|
if (!taskInfo) return { success: false, error: `Task ${taskId} not found` };
|
|
21394
21233
|
const task = taskInfo.task;
|
|
21395
21234
|
const columnId = taskInfo.column.id;
|
|
21396
|
-
const result = (0,
|
|
21235
|
+
const result = (0, import_core3.deleteTask)(board, columnId, taskId);
|
|
21397
21236
|
if (!result.success) return { success: false, error: result.error };
|
|
21398
21237
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21399
21238
|
if (!writeResult.success) return writeResult;
|
|
21400
21239
|
const archivePath = getArchivePath(filePath);
|
|
21401
21240
|
let archiveBoard;
|
|
21402
|
-
if (
|
|
21241
|
+
if (fs3.existsSync(archivePath)) {
|
|
21403
21242
|
try {
|
|
21404
|
-
const archiveContent =
|
|
21405
|
-
const parseResult =
|
|
21243
|
+
const archiveContent = fs3.readFileSync(archivePath, "utf-8");
|
|
21244
|
+
const parseResult = import_core3.Brainfile.parseWithErrors(archiveContent);
|
|
21406
21245
|
if (parseResult.board) {
|
|
21407
21246
|
archiveBoard = parseResult.board;
|
|
21408
21247
|
} else {
|
|
@@ -21419,8 +21258,8 @@ function archiveTaskAction(filePath, taskId, options) {
|
|
|
21419
21258
|
}
|
|
21420
21259
|
archiveBoard.archive.unshift(task);
|
|
21421
21260
|
try {
|
|
21422
|
-
const archiveContent =
|
|
21423
|
-
|
|
21261
|
+
const archiveContent = import_core3.Brainfile.serialize(archiveBoard);
|
|
21262
|
+
fs3.writeFileSync(archivePath, archiveContent, "utf-8");
|
|
21424
21263
|
} catch (err) {
|
|
21425
21264
|
return {
|
|
21426
21265
|
success: false,
|
|
@@ -21432,7 +21271,7 @@ function archiveTaskAction(filePath, taskId, options) {
|
|
|
21432
21271
|
async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
21433
21272
|
const { board, error } = readBoardState(filePath);
|
|
21434
21273
|
if (!board) return { success: false, error };
|
|
21435
|
-
const taskInfo = (0,
|
|
21274
|
+
const taskInfo = (0, import_core3.findTaskById)(board, taskId);
|
|
21436
21275
|
if (!taskInfo) return { success: false, error: `Task ${taskId} not found` };
|
|
21437
21276
|
const task = taskInfo.task;
|
|
21438
21277
|
const columnId = taskInfo.column.id;
|
|
@@ -21453,7 +21292,7 @@ async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
|
21453
21292
|
if (!owner || !repo) {
|
|
21454
21293
|
return { success: false, error: "GitHub owner/repo not configured. Set archive.destination: github:owner/repo" };
|
|
21455
21294
|
}
|
|
21456
|
-
const payload = (0,
|
|
21295
|
+
const payload = (0, import_core3.formatTaskForGitHub)(task, {
|
|
21457
21296
|
includeMeta: true,
|
|
21458
21297
|
includeSubtasks: true,
|
|
21459
21298
|
includeRelatedFiles: true,
|
|
@@ -21475,10 +21314,10 @@ async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
|
21475
21314
|
if ((0, import_core.isV2)(filePath)) {
|
|
21476
21315
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21477
21316
|
if (located) {
|
|
21478
|
-
(0,
|
|
21317
|
+
(0, import_core3.deleteTaskFile)(located.taskPath);
|
|
21479
21318
|
}
|
|
21480
21319
|
} else {
|
|
21481
|
-
const deleteResult = (0,
|
|
21320
|
+
const deleteResult = (0, import_core3.deleteTask)(board, columnId, taskId);
|
|
21482
21321
|
if (deleteResult.success) {
|
|
21483
21322
|
writeBrainfile(filePath, deleteResult.board);
|
|
21484
21323
|
}
|
|
@@ -21512,7 +21351,7 @@ async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
|
21512
21351
|
return { success: false, error: "No Linear teams found" };
|
|
21513
21352
|
}
|
|
21514
21353
|
}
|
|
21515
|
-
const payload = (0,
|
|
21354
|
+
const payload = (0, import_core3.formatTaskForLinear)(task, {
|
|
21516
21355
|
includeMeta: true,
|
|
21517
21356
|
includeSubtasks: true,
|
|
21518
21357
|
includeRelatedFiles: true,
|
|
@@ -21534,10 +21373,10 @@ async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
|
21534
21373
|
if ((0, import_core.isV2)(filePath)) {
|
|
21535
21374
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21536
21375
|
if (located) {
|
|
21537
|
-
(0,
|
|
21376
|
+
(0, import_core3.deleteTaskFile)(located.taskPath);
|
|
21538
21377
|
}
|
|
21539
21378
|
} else {
|
|
21540
|
-
const deleteResult = (0,
|
|
21379
|
+
const deleteResult = (0, import_core3.deleteTask)(board, columnId, taskId);
|
|
21541
21380
|
if (deleteResult.success) {
|
|
21542
21381
|
writeBrainfile(filePath, deleteResult.board);
|
|
21543
21382
|
}
|
|
@@ -21546,53 +21385,29 @@ async function archiveTaskActionAsync(filePath, taskId, options) {
|
|
|
21546
21385
|
}
|
|
21547
21386
|
return { success: false, error: `Unknown destination: ${destination}` };
|
|
21548
21387
|
}
|
|
21549
|
-
function patchTaskAction(filePath, taskId, patch) {
|
|
21550
|
-
if ((0, import_core.isV2)(filePath)) {
|
|
21551
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
21552
|
-
if (!located) return { success: false, error: `Task ${taskId} not found` };
|
|
21553
|
-
const result2 = (0, import_core4.patchTaskFile)(located.taskPath, {
|
|
21554
|
-
title: patch.title,
|
|
21555
|
-
description: patch.description,
|
|
21556
|
-
priority: patch.priority,
|
|
21557
|
-
tags: patch.tags,
|
|
21558
|
-
assignee: patch.assignee,
|
|
21559
|
-
dueDate: patch.dueDate,
|
|
21560
|
-
relatedFiles: patch.relatedFiles
|
|
21561
|
-
});
|
|
21562
|
-
if (!result2.success) return { success: false, error: result2.error };
|
|
21563
|
-
return { success: true, message: `Updated ${taskId}` };
|
|
21564
|
-
}
|
|
21565
|
-
const { board, error } = readBrainfile(filePath);
|
|
21566
|
-
if (!board) return { success: false, error };
|
|
21567
|
-
const result = (0, import_core4.patchTask)(board, taskId, patch);
|
|
21568
|
-
if (!result.success) return { success: false, error: result.error };
|
|
21569
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
21570
|
-
if (!writeResult.success) return writeResult;
|
|
21571
|
-
return { success: true, message: `Updated ${taskId}` };
|
|
21572
|
-
}
|
|
21573
21388
|
function cyclePriorityAction(filePath, taskId) {
|
|
21574
21389
|
const priorities = [void 0, "low", "medium", "high", "critical"];
|
|
21575
21390
|
if ((0, import_core.isV2)(filePath)) {
|
|
21576
21391
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21577
21392
|
if (!located) return { success: false, error: `Task ${taskId} not found` };
|
|
21578
|
-
const doc = (0,
|
|
21393
|
+
const doc = (0, import_core3.readTaskFile)(located.taskPath);
|
|
21579
21394
|
if (!doc) return { success: false, error: `Failed to read task ${taskId}` };
|
|
21580
21395
|
const currentIndex2 = priorities.indexOf(doc.task.priority);
|
|
21581
21396
|
const nextIndex2 = (currentIndex2 + 1) % priorities.length;
|
|
21582
21397
|
const nextPriority2 = priorities[nextIndex2];
|
|
21583
|
-
const result2 = (0,
|
|
21398
|
+
const result2 = (0, import_core3.patchTaskFile)(located.taskPath, { priority: nextPriority2 ?? null });
|
|
21584
21399
|
if (!result2.success) return { success: false, error: result2.error };
|
|
21585
21400
|
return { success: true, message: `Priority: ${nextPriority2 || "none"}` };
|
|
21586
21401
|
}
|
|
21587
21402
|
const { board, error } = readBrainfile(filePath);
|
|
21588
21403
|
if (!board) return { success: false, error };
|
|
21589
|
-
const taskInfo = (0,
|
|
21404
|
+
const taskInfo = (0, import_core3.findTaskById)(board, taskId);
|
|
21590
21405
|
if (!taskInfo) return { success: false, error: `Task ${taskId} not found` };
|
|
21591
21406
|
const currentIndex = priorities.indexOf(taskInfo.task.priority);
|
|
21592
21407
|
const nextIndex = (currentIndex + 1) % priorities.length;
|
|
21593
21408
|
const nextPriority = priorities[nextIndex];
|
|
21594
21409
|
const patch = { priority: nextPriority || null };
|
|
21595
|
-
const result = (0,
|
|
21410
|
+
const result = (0, import_core3.patchTask)(board, taskId, patch);
|
|
21596
21411
|
if (!result.success) return { success: false, error: result.error };
|
|
21597
21412
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21598
21413
|
if (!writeResult.success) return writeResult;
|
|
@@ -21602,7 +21417,7 @@ function toggleSubtaskAction(filePath, taskId, subtaskId) {
|
|
|
21602
21417
|
if ((0, import_core.isV2)(filePath)) {
|
|
21603
21418
|
const located = resolveV2TaskPath(filePath, taskId);
|
|
21604
21419
|
if (!located) return { success: false, error: `Task ${taskId} not found` };
|
|
21605
|
-
const result2 = (0,
|
|
21420
|
+
const result2 = (0, import_core3.toggleSubtasksInFile)(located.taskPath, [subtaskId]);
|
|
21606
21421
|
if (!result2.success) {
|
|
21607
21422
|
return {
|
|
21608
21423
|
success: false,
|
|
@@ -21613,7 +21428,7 @@ function toggleSubtaskAction(filePath, taskId, subtaskId) {
|
|
|
21613
21428
|
}
|
|
21614
21429
|
const { board, error } = readBrainfile(filePath);
|
|
21615
21430
|
if (!board) return { success: false, error };
|
|
21616
|
-
const result = (0,
|
|
21431
|
+
const result = (0, import_core3.toggleSubtask)(board, taskId, subtaskId);
|
|
21617
21432
|
if (!result.success) return { success: false, error: result.error };
|
|
21618
21433
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21619
21434
|
if (!writeResult.success) return writeResult;
|
|
@@ -21622,11 +21437,11 @@ function toggleSubtaskAction(filePath, taskId, subtaskId) {
|
|
|
21622
21437
|
function addTaskAction(filePath, columnId, taskInput) {
|
|
21623
21438
|
const { board, error } = readBoardState(filePath);
|
|
21624
21439
|
if (!board) return { success: false, error };
|
|
21625
|
-
const column = (0,
|
|
21440
|
+
const column = (0, import_core3.findColumnById)(board, columnId) || (0, import_core3.findColumnByName)(board, columnId);
|
|
21626
21441
|
if (!column) return { success: false, error: `Column ${columnId} not found` };
|
|
21627
21442
|
if ((0, import_core.isV2)(filePath)) {
|
|
21628
21443
|
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
21629
|
-
const result2 = (0,
|
|
21444
|
+
const result2 = (0, import_core3.addTaskFile)(
|
|
21630
21445
|
dirs.boardDir,
|
|
21631
21446
|
{
|
|
21632
21447
|
title: taskInput.title,
|
|
@@ -21646,7 +21461,7 @@ ${taskInput.description.trim()}
|
|
|
21646
21461
|
if (!result2.success) return { success: false, error: result2.error };
|
|
21647
21462
|
return { success: true, message: `Added ${result2.task?.id || "task"}` };
|
|
21648
21463
|
}
|
|
21649
|
-
const result = (0,
|
|
21464
|
+
const result = (0, import_core3.addTask)(board, column.id, taskInput);
|
|
21650
21465
|
if (!result.success) return { success: false, error: result.error };
|
|
21651
21466
|
const writeResult = writeBrainfile(filePath, result.board);
|
|
21652
21467
|
if (!writeResult.success) return writeResult;
|
|
@@ -21654,262 +21469,19 @@ ${taskInput.description.trim()}
|
|
|
21654
21469
|
const newTask = newColumn?.tasks[newColumn.tasks.length - 1];
|
|
21655
21470
|
return { success: true, message: `Added ${newTask?.id || "task"}` };
|
|
21656
21471
|
}
|
|
21657
|
-
function
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
const tempDir = os3.tmpdir();
|
|
21664
|
-
const tempFile = path2.join(tempDir, `brainfile-new-task.yaml`);
|
|
21665
|
-
try {
|
|
21666
|
-
fs4.writeFileSync(tempFile, template, "utf-8");
|
|
21667
|
-
} catch (err) {
|
|
21668
|
-
return { success: false, error: `Failed to create temp file: ${err}` };
|
|
21669
|
-
}
|
|
21670
|
-
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
21671
|
-
const result = spawnSync(editor, [tempFile], {
|
|
21672
|
-
stdio: "inherit",
|
|
21673
|
-
shell: true,
|
|
21674
|
-
timeout: 3e5
|
|
21675
|
-
});
|
|
21676
|
-
process.stdout.write("\x1B[?25l");
|
|
21677
|
-
process.stdout.write("\x1B[2K");
|
|
21678
|
-
if (result.error) {
|
|
21679
|
-
cleanupTempFile(tempFile);
|
|
21680
|
-
return { success: false, error: `Failed to open editor: ${result.error.message}` };
|
|
21681
|
-
}
|
|
21682
|
-
if (result.signal === "SIGTERM") {
|
|
21683
|
-
cleanupTempFile(tempFile);
|
|
21684
|
-
return { success: false, error: "Editor timed out (5 minute limit)" };
|
|
21685
|
-
}
|
|
21686
|
-
if (result.status !== 0) {
|
|
21687
|
-
cleanupTempFile(tempFile);
|
|
21688
|
-
return { success: false, error: `Editor exited with code ${result.status}` };
|
|
21689
|
-
}
|
|
21690
|
-
let editedYaml;
|
|
21691
|
-
try {
|
|
21692
|
-
editedYaml = fs4.readFileSync(tempFile, "utf-8");
|
|
21693
|
-
} catch (err) {
|
|
21694
|
-
cleanupTempFile(tempFile);
|
|
21695
|
-
return { success: false, error: `Failed to read edited file: ${err}` };
|
|
21696
|
-
}
|
|
21697
|
-
cleanupTempFile(tempFile);
|
|
21698
|
-
const parseResult = parseNewTaskYaml(editedYaml);
|
|
21699
|
-
if (!parseResult.success) {
|
|
21700
|
-
return { success: false, error: parseResult.error };
|
|
21701
|
-
}
|
|
21702
|
-
if (!parseResult.taskInput || !parseResult.taskInput.title) {
|
|
21703
|
-
return { success: false, error: "Task title is required" };
|
|
21704
|
-
}
|
|
21705
|
-
return addTaskAction(filePath, columnId, parseResult.taskInput);
|
|
21706
|
-
}
|
|
21707
|
-
function buildNewTaskTemplate() {
|
|
21708
|
-
return `# New Task
|
|
21709
|
-
# Fill in the fields below. Save and close to create the task.
|
|
21710
|
-
# Lines starting with # are ignored. Title is required.
|
|
21711
|
-
|
|
21712
|
-
title:
|
|
21713
|
-
description:
|
|
21714
|
-
priority:
|
|
21715
|
-
tags:
|
|
21716
|
-
assignee:
|
|
21717
|
-
dueDate:
|
|
21718
|
-
`;
|
|
21719
|
-
}
|
|
21720
|
-
function parseNewTaskYaml(yaml) {
|
|
21721
|
-
try {
|
|
21722
|
-
const lines = yaml.split("\n").filter((line) => !line.startsWith("#") && line.trim());
|
|
21723
|
-
const taskInput = { title: "" };
|
|
21724
|
-
for (const line of lines) {
|
|
21725
|
-
const colonIndex = line.indexOf(":");
|
|
21726
|
-
if (colonIndex === -1) continue;
|
|
21727
|
-
const key = line.substring(0, colonIndex).trim();
|
|
21728
|
-
const value = line.substring(colonIndex + 1).trim();
|
|
21729
|
-
switch (key) {
|
|
21730
|
-
case "title":
|
|
21731
|
-
if (value) taskInput.title = value;
|
|
21732
|
-
break;
|
|
21733
|
-
case "description":
|
|
21734
|
-
if (value) taskInput.description = value;
|
|
21735
|
-
break;
|
|
21736
|
-
case "priority":
|
|
21737
|
-
const validPriorities = ["low", "medium", "high", "critical"];
|
|
21738
|
-
if (validPriorities.includes(value)) {
|
|
21739
|
-
taskInput.priority = value;
|
|
21740
|
-
}
|
|
21741
|
-
break;
|
|
21742
|
-
case "tags":
|
|
21743
|
-
if (value) {
|
|
21744
|
-
taskInput.tags = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
21745
|
-
}
|
|
21746
|
-
break;
|
|
21747
|
-
case "assignee":
|
|
21748
|
-
if (value) taskInput.assignee = value;
|
|
21749
|
-
break;
|
|
21750
|
-
case "dueDate":
|
|
21751
|
-
if (value) taskInput.dueDate = value;
|
|
21752
|
-
break;
|
|
21753
|
-
}
|
|
21754
|
-
}
|
|
21755
|
-
return { success: true, taskInput };
|
|
21756
|
-
} catch (err) {
|
|
21757
|
-
return { success: false, error: `Failed to parse task: ${err}` };
|
|
21758
|
-
}
|
|
21759
|
-
}
|
|
21760
|
-
function editTaskInEditor(filePath, taskId) {
|
|
21761
|
-
const { board, error } = readBoardState(filePath);
|
|
21762
|
-
if (!board) {
|
|
21763
|
-
return { success: false, error };
|
|
21764
|
-
}
|
|
21765
|
-
const taskInfo = (0, import_core4.findTaskById)(board, taskId);
|
|
21766
|
-
if (!taskInfo) {
|
|
21767
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
21768
|
-
}
|
|
21769
|
-
const task = taskInfo.task;
|
|
21770
|
-
const taskYaml = buildTaskYaml(task);
|
|
21771
|
-
const tempDir = os3.tmpdir();
|
|
21772
|
-
const tempFile = path2.join(tempDir, `brainfile-edit-${taskId}.yaml`);
|
|
21773
|
-
try {
|
|
21774
|
-
fs4.writeFileSync(tempFile, taskYaml, "utf-8");
|
|
21775
|
-
} catch (err) {
|
|
21776
|
-
return { success: false, error: `Failed to create temp file: ${err}` };
|
|
21777
|
-
}
|
|
21778
|
-
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
21779
|
-
const result = spawnSync(editor, [tempFile], {
|
|
21780
|
-
stdio: "inherit",
|
|
21781
|
-
shell: true,
|
|
21782
|
-
timeout: 3e5
|
|
21783
|
-
});
|
|
21784
|
-
process.stdout.write("\x1B[?25l");
|
|
21785
|
-
process.stdout.write("\x1B[2K");
|
|
21786
|
-
if (result.error) {
|
|
21787
|
-
cleanupTempFile(tempFile);
|
|
21788
|
-
return { success: false, error: `Failed to open editor: ${result.error.message}` };
|
|
21789
|
-
}
|
|
21790
|
-
if (result.signal === "SIGTERM") {
|
|
21791
|
-
cleanupTempFile(tempFile);
|
|
21792
|
-
return { success: false, error: "Editor timed out (5 minute limit)" };
|
|
21793
|
-
}
|
|
21794
|
-
if (result.status !== 0) {
|
|
21795
|
-
cleanupTempFile(tempFile);
|
|
21796
|
-
return { success: false, error: `Editor exited with code ${result.status}` };
|
|
21797
|
-
}
|
|
21798
|
-
let editedYaml;
|
|
21799
|
-
try {
|
|
21800
|
-
editedYaml = fs4.readFileSync(tempFile, "utf-8");
|
|
21801
|
-
} catch (err) {
|
|
21802
|
-
cleanupTempFile(tempFile);
|
|
21803
|
-
return { success: false, error: `Failed to read edited file: ${err}` };
|
|
21804
|
-
}
|
|
21805
|
-
cleanupTempFile(tempFile);
|
|
21806
|
-
const patchResult = parseEditedTaskYaml(editedYaml, task);
|
|
21807
|
-
if (!patchResult.success) {
|
|
21808
|
-
return { success: false, error: patchResult.error };
|
|
21809
|
-
}
|
|
21810
|
-
if (!patchResult.patch || Object.keys(patchResult.patch).length === 0) {
|
|
21811
|
-
return { success: true, message: "No changes made" };
|
|
21812
|
-
}
|
|
21813
|
-
return patchTaskAction(filePath, taskId, patchResult.patch);
|
|
21814
|
-
}
|
|
21815
|
-
function buildTaskYaml(task) {
|
|
21816
|
-
const lines = [
|
|
21817
|
-
`# Edit task: ${task.id}`,
|
|
21818
|
-
`# Save and close to apply changes. Delete a field to clear it.`,
|
|
21819
|
-
`# Lines starting with # are ignored.`,
|
|
21820
|
-
``,
|
|
21821
|
-
`title: ${task.title}`
|
|
21822
|
-
];
|
|
21823
|
-
if (task.description) {
|
|
21824
|
-
if (task.description.includes("\n")) {
|
|
21825
|
-
lines.push(`description: |`);
|
|
21826
|
-
task.description.split("\n").forEach((line) => {
|
|
21827
|
-
lines.push(` ${line}`);
|
|
21828
|
-
});
|
|
21829
|
-
} else {
|
|
21830
|
-
lines.push(`description: ${task.description}`);
|
|
21831
|
-
}
|
|
21832
|
-
} else {
|
|
21833
|
-
lines.push(`# description: `);
|
|
21834
|
-
}
|
|
21835
|
-
lines.push(`priority: ${task.priority || ""}`);
|
|
21836
|
-
lines.push(`tags: ${task.tags?.join(", ") || ""}`);
|
|
21837
|
-
lines.push(`assignee: ${task.assignee || ""}`);
|
|
21838
|
-
lines.push(`dueDate: ${task.dueDate || ""}`);
|
|
21839
|
-
if (task.subtasks && task.subtasks.length > 0) {
|
|
21840
|
-
lines.push(`subtasks:`);
|
|
21841
|
-
task.subtasks.forEach((st) => {
|
|
21842
|
-
lines.push(` - [${st.completed ? "x" : " "}] ${st.title}`);
|
|
21843
|
-
});
|
|
21844
|
-
}
|
|
21845
|
-
if (task.relatedFiles && task.relatedFiles.length > 0) {
|
|
21846
|
-
lines.push(`relatedFiles: ${task.relatedFiles.join(", ")}`);
|
|
21472
|
+
function resolveTaskFilePath(filePath, taskId) {
|
|
21473
|
+
if (!(0, import_core.isV2)(filePath)) {
|
|
21474
|
+
return {
|
|
21475
|
+
success: false,
|
|
21476
|
+
error: "Editing a document file needs a v2 board. Run: brainfile migrate"
|
|
21477
|
+
};
|
|
21847
21478
|
}
|
|
21848
|
-
return lines.join("\n") + "\n";
|
|
21849
|
-
}
|
|
21850
|
-
function parseEditedTaskYaml(yaml, originalTask) {
|
|
21851
|
-
const patch = {};
|
|
21852
21479
|
try {
|
|
21853
|
-
const
|
|
21854
|
-
|
|
21855
|
-
|
|
21856
|
-
if (colonIndex === -1) continue;
|
|
21857
|
-
const key = line.substring(0, colonIndex).trim();
|
|
21858
|
-
const value = line.substring(colonIndex + 1).trim();
|
|
21859
|
-
switch (key) {
|
|
21860
|
-
case "title":
|
|
21861
|
-
if (value && value !== originalTask.title) {
|
|
21862
|
-
patch.title = value;
|
|
21863
|
-
}
|
|
21864
|
-
break;
|
|
21865
|
-
case "description":
|
|
21866
|
-
if (value !== (originalTask.description || "")) {
|
|
21867
|
-
patch.description = value || void 0;
|
|
21868
|
-
}
|
|
21869
|
-
break;
|
|
21870
|
-
case "priority":
|
|
21871
|
-
const validPriorities = ["low", "medium", "high", "critical", ""];
|
|
21872
|
-
if (validPriorities.includes(value) && value !== (originalTask.priority || "")) {
|
|
21873
|
-
patch.priority = value || null;
|
|
21874
|
-
}
|
|
21875
|
-
break;
|
|
21876
|
-
case "tags":
|
|
21877
|
-
const newTags = value ? value.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
21878
|
-
const oldTags = originalTask.tags || [];
|
|
21879
|
-
if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
|
|
21880
|
-
patch.tags = newTags.length > 0 ? newTags : null;
|
|
21881
|
-
}
|
|
21882
|
-
break;
|
|
21883
|
-
case "assignee":
|
|
21884
|
-
if (value !== (originalTask.assignee || "")) {
|
|
21885
|
-
patch.assignee = value || null;
|
|
21886
|
-
}
|
|
21887
|
-
break;
|
|
21888
|
-
case "dueDate":
|
|
21889
|
-
if (value !== (originalTask.dueDate || "")) {
|
|
21890
|
-
patch.dueDate = value || null;
|
|
21891
|
-
}
|
|
21892
|
-
break;
|
|
21893
|
-
case "relatedFiles":
|
|
21894
|
-
const newFiles = value ? value.split(",").map((f) => f.trim()).filter(Boolean) : [];
|
|
21895
|
-
const oldFiles = originalTask.relatedFiles || [];
|
|
21896
|
-
if (JSON.stringify(newFiles) !== JSON.stringify(oldFiles)) {
|
|
21897
|
-
patch.relatedFiles = newFiles.length > 0 ? newFiles : null;
|
|
21898
|
-
}
|
|
21899
|
-
break;
|
|
21900
|
-
}
|
|
21901
|
-
}
|
|
21902
|
-
return { success: true, patch };
|
|
21480
|
+
const found = (0, import_core.findV2Task)((0, import_core.getV2Dirs)(filePath), taskId, true);
|
|
21481
|
+
if (!found) return { success: false, error: `Document ${taskId} not found` };
|
|
21482
|
+
return { success: true, path: found.filePath };
|
|
21903
21483
|
} catch (err) {
|
|
21904
|
-
return { success: false, error:
|
|
21905
|
-
}
|
|
21906
|
-
}
|
|
21907
|
-
function cleanupTempFile(filePath) {
|
|
21908
|
-
try {
|
|
21909
|
-
if (fs4.existsSync(filePath)) {
|
|
21910
|
-
fs4.unlinkSync(filePath);
|
|
21911
|
-
}
|
|
21912
|
-
} catch {
|
|
21484
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
21913
21485
|
}
|
|
21914
21486
|
}
|
|
21915
21487
|
function copyToClipboard(text) {
|
|
@@ -21941,66 +21513,38 @@ function copyToClipboard(text) {
|
|
|
21941
21513
|
return { success: false, error: `Failed to copy: ${err}` };
|
|
21942
21514
|
}
|
|
21943
21515
|
}
|
|
21944
|
-
function
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
function updateRuleAction(filePath, ruleType, ruleId, ruleText) {
|
|
21970
|
-
const { board, error } = readBoardConfigOnly(filePath);
|
|
21971
|
-
if (!board) return { success: false, error };
|
|
21972
|
-
if (!board.rules || !board.rules[ruleType]) {
|
|
21973
|
-
return { success: false, error: `No ${ruleType} rules found` };
|
|
21974
|
-
}
|
|
21975
|
-
const rule = board.rules[ruleType].find((r) => r.id === ruleId);
|
|
21976
|
-
if (!rule) {
|
|
21977
|
-
return { success: false, error: `Rule #${ruleId} not found` };
|
|
21978
|
-
}
|
|
21979
|
-
rule.rule = ruleText.trim();
|
|
21980
|
-
const writeResult = writeBrainfile(filePath, board);
|
|
21981
|
-
if (!writeResult.success) return writeResult;
|
|
21982
|
-
return { success: true, message: `Updated ${ruleType} rule #${ruleId}` };
|
|
21983
|
-
}
|
|
21984
|
-
function deleteRuleAction(filePath, ruleType, ruleId) {
|
|
21985
|
-
const { board, error } = readBoardConfigOnly(filePath);
|
|
21986
|
-
if (!board) return { success: false, error };
|
|
21987
|
-
if (!board.rules || !board.rules[ruleType]) {
|
|
21988
|
-
return { success: false, error: `No ${ruleType} rules found` };
|
|
21989
|
-
}
|
|
21990
|
-
const ruleIndex = board.rules[ruleType].findIndex((r) => r.id === ruleId);
|
|
21991
|
-
if (ruleIndex === -1) {
|
|
21992
|
-
return { success: false, error: `Rule #${ruleId} not found` };
|
|
21516
|
+
function getTaskActivity(filePath, taskId, limit = 5) {
|
|
21517
|
+
if (!(0, import_core.isV2)(filePath)) return [];
|
|
21518
|
+
try {
|
|
21519
|
+
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
21520
|
+
const doc = (0, import_core3.findTask)(dirs.boardDir, taskId) ?? (0, import_core3.findTask)(dirs.logsDir, taskId);
|
|
21521
|
+
const entries = [];
|
|
21522
|
+
if (doc) {
|
|
21523
|
+
for (const note of (0, import_core3.parseNotes)(doc.body)) {
|
|
21524
|
+
entries.push({
|
|
21525
|
+
at: note.at,
|
|
21526
|
+
text: note.agent ? `[${note.agent}] ${note.text}` : note.text
|
|
21527
|
+
});
|
|
21528
|
+
}
|
|
21529
|
+
}
|
|
21530
|
+
try {
|
|
21531
|
+
for (const record of (0, import_core3.readLedger)(dirs.logsDir)) {
|
|
21532
|
+
if (record.id !== taskId) continue;
|
|
21533
|
+
entries.push({ at: record.completedAt, text: `completed \xB7 ${record.summary}` });
|
|
21534
|
+
}
|
|
21535
|
+
} catch {
|
|
21536
|
+
}
|
|
21537
|
+
entries.sort((a, b) => (Date.parse(b.at ?? "") || 0) - (Date.parse(a.at ?? "") || 0));
|
|
21538
|
+
return entries.slice(0, limit);
|
|
21539
|
+
} catch {
|
|
21540
|
+
return [];
|
|
21993
21541
|
}
|
|
21994
|
-
board.rules[ruleType].splice(ruleIndex, 1);
|
|
21995
|
-
const writeResult = writeBrainfile(filePath, board);
|
|
21996
|
-
if (!writeResult.success) return writeResult;
|
|
21997
|
-
return { success: true, message: `Deleted ${ruleType} rule #${ruleId}` };
|
|
21998
21542
|
}
|
|
21999
21543
|
function loadLogs(filePath) {
|
|
22000
21544
|
if ((0, import_core.isV2)(filePath)) {
|
|
22001
21545
|
try {
|
|
22002
21546
|
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
22003
|
-
const docs = (0,
|
|
21547
|
+
const docs = (0, import_core3.readTasksDir)(dirs.logsDir);
|
|
22004
21548
|
const logs = docs.map((doc) => ({ ...doc.task })).sort((a, b) => {
|
|
22005
21549
|
const aTime = a.completedAt ? Date.parse(a.completedAt) : 0;
|
|
22006
21550
|
const bTime = b.completedAt ? Date.parse(b.completedAt) : 0;
|
|
@@ -22012,12 +21556,12 @@ function loadLogs(filePath) {
|
|
|
22012
21556
|
}
|
|
22013
21557
|
}
|
|
22014
21558
|
const archivePath = getArchivePath(filePath);
|
|
22015
|
-
if (!
|
|
21559
|
+
if (!fs3.existsSync(archivePath)) {
|
|
22016
21560
|
return { logs: [] };
|
|
22017
21561
|
}
|
|
22018
21562
|
try {
|
|
22019
|
-
const archiveContent =
|
|
22020
|
-
const parseResult =
|
|
21563
|
+
const archiveContent = fs3.readFileSync(archivePath, "utf-8");
|
|
21564
|
+
const parseResult = import_core3.Brainfile.parseWithErrors(archiveContent);
|
|
22021
21565
|
if (parseResult.board && parseResult.board.archive) {
|
|
22022
21566
|
return { logs: parseResult.board.archive };
|
|
22023
21567
|
}
|
|
@@ -22026,125 +21570,453 @@ function loadLogs(filePath) {
|
|
|
22026
21570
|
return { logs: [], error: err instanceof Error ? err.message : String(err) };
|
|
22027
21571
|
}
|
|
22028
21572
|
}
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
22044
|
-
}
|
|
22045
|
-
const restoredTask = {
|
|
22046
|
-
...logDoc.task,
|
|
22047
|
-
column: column2.id,
|
|
22048
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22049
|
-
};
|
|
22050
|
-
delete restoredTask.completedAt;
|
|
22051
|
-
const targetPath = path2.join(dirs.boardDir, `${taskId}.md`);
|
|
22052
|
-
try {
|
|
22053
|
-
(0, import_core4.writeTaskFile)(targetPath, restoredTask, logDoc.body);
|
|
22054
|
-
fs4.unlinkSync(logDoc.filePath);
|
|
22055
|
-
return { success: true, message: `Restored ${taskId} to ${column2.title}` };
|
|
22056
|
-
} catch (err) {
|
|
22057
|
-
return { success: false, error: `Failed to restore task: ${err instanceof Error ? err.message : String(err)}` };
|
|
21573
|
+
|
|
21574
|
+
// src/tui/hooks/useBrainfileLoader.ts
|
|
21575
|
+
function getV2ChangeSignature(filePath) {
|
|
21576
|
+
const dirs = (0, import_core.getV2Dirs)(filePath);
|
|
21577
|
+
const parts = [];
|
|
21578
|
+
try {
|
|
21579
|
+
const boardEntries = fs4.existsSync(dirs.boardDir) ? fs4.readdirSync(dirs.boardDir).filter((name) => name.endsWith(".md")).sort() : [];
|
|
21580
|
+
for (const name of boardEntries) {
|
|
21581
|
+
const fullPath = path2.join(dirs.boardDir, name);
|
|
21582
|
+
try {
|
|
21583
|
+
const stat4 = fs4.statSync(fullPath);
|
|
21584
|
+
parts.push(`board:${name}:${stat4.mtimeMs}:${stat4.size}`);
|
|
21585
|
+
} catch {
|
|
21586
|
+
}
|
|
22058
21587
|
}
|
|
21588
|
+
} catch {
|
|
22059
21589
|
}
|
|
22060
|
-
const archivePath = getArchivePath(filePath);
|
|
22061
|
-
if (!fs4.existsSync(archivePath)) {
|
|
22062
|
-
return { success: false, error: "Logs file not found" };
|
|
22063
|
-
}
|
|
22064
|
-
let archiveBoard;
|
|
22065
21590
|
try {
|
|
22066
|
-
const
|
|
22067
|
-
const
|
|
22068
|
-
|
|
22069
|
-
|
|
21591
|
+
const logsEntries = fs4.existsSync(dirs.logsDir) ? fs4.readdirSync(dirs.logsDir).filter((name) => name.endsWith(".md")).sort() : [];
|
|
21592
|
+
for (const name of logsEntries) {
|
|
21593
|
+
const fullPath = path2.join(dirs.logsDir, name);
|
|
21594
|
+
try {
|
|
21595
|
+
const stat4 = fs4.statSync(fullPath);
|
|
21596
|
+
parts.push(`logs:${name}:${stat4.mtimeMs}:${stat4.size}`);
|
|
21597
|
+
} catch {
|
|
21598
|
+
}
|
|
22070
21599
|
}
|
|
22071
|
-
|
|
22072
|
-
} catch (err) {
|
|
22073
|
-
return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
|
|
22074
|
-
}
|
|
22075
|
-
if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
|
|
22076
|
-
return { success: false, error: "Logs are empty" };
|
|
22077
|
-
}
|
|
22078
|
-
const taskIndex = archiveBoard.archive.findIndex((t) => t.id === taskId);
|
|
22079
|
-
if (taskIndex === -1) {
|
|
22080
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
22081
|
-
}
|
|
22082
|
-
const task = archiveBoard.archive[taskIndex];
|
|
22083
|
-
const { board: mainBoard, error: mainBoardError } = readBrainfile(filePath);
|
|
22084
|
-
if (!mainBoard) return { success: false, error: mainBoardError };
|
|
22085
|
-
const column = (0, import_core4.findColumnById)(mainBoard, toColumnId) || (0, import_core4.findColumnByName)(mainBoard, toColumnId);
|
|
22086
|
-
if (!column) {
|
|
22087
|
-
return { success: false, error: `Column ${toColumnId} not found` };
|
|
22088
|
-
}
|
|
22089
|
-
column.tasks.unshift(task);
|
|
22090
|
-
archiveBoard.archive.splice(taskIndex, 1);
|
|
22091
|
-
const writeResult = writeBrainfile(filePath, mainBoard);
|
|
22092
|
-
if (!writeResult.success) return writeResult;
|
|
22093
|
-
try {
|
|
22094
|
-
const archiveContent = import_core4.Brainfile.serialize(archiveBoard);
|
|
22095
|
-
fs4.writeFileSync(archivePath, archiveContent, "utf-8");
|
|
22096
|
-
} catch (err) {
|
|
22097
|
-
return { success: false, error: `Task restored but failed to update logs: ${err}` };
|
|
21600
|
+
} catch {
|
|
22098
21601
|
}
|
|
22099
|
-
return
|
|
21602
|
+
return parts.join("|");
|
|
22100
21603
|
}
|
|
22101
|
-
function
|
|
22102
|
-
|
|
22103
|
-
|
|
22104
|
-
|
|
22105
|
-
|
|
22106
|
-
|
|
21604
|
+
function useBrainfileLoader(filePath, state, setState) {
|
|
21605
|
+
const flashTimeoutRef = (0, import_react34.useRef)(null);
|
|
21606
|
+
const loadBrainfile = (0, import_react34.useCallback)((forceRefresh = false) => {
|
|
21607
|
+
try {
|
|
21608
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
21609
|
+
const v2 = (0, import_core.isV2)(filePath);
|
|
21610
|
+
let board = null;
|
|
21611
|
+
let parseError;
|
|
21612
|
+
if (v2) {
|
|
21613
|
+
try {
|
|
21614
|
+
board = (0, import_core.buildBoardFromV2)(filePath);
|
|
21615
|
+
} catch (err) {
|
|
21616
|
+
parseError = err instanceof Error ? err.message : String(err);
|
|
21617
|
+
}
|
|
21618
|
+
} else {
|
|
21619
|
+
const result = import_core4.Brainfile.parseWithErrors(content);
|
|
21620
|
+
if (result.board) {
|
|
21621
|
+
board = result.board;
|
|
21622
|
+
} else {
|
|
21623
|
+
parseError = result.error || "Not a valid brainfile";
|
|
21624
|
+
}
|
|
21625
|
+
}
|
|
21626
|
+
const contentHash = v2 ? (0, import_core4.hashBoardContent)(`${content}
|
|
21627
|
+
${getV2ChangeSignature(filePath)}`) : (0, import_core4.hashBoardContent)(content);
|
|
21628
|
+
const logs = loadLogs(filePath).logs;
|
|
21629
|
+
setState((prev) => {
|
|
21630
|
+
if (!forceRefresh && prev.lastContentHash === contentHash) {
|
|
21631
|
+
return prev;
|
|
21632
|
+
}
|
|
21633
|
+
if (board) {
|
|
21634
|
+
if (prev.doneView) {
|
|
21635
|
+
const prevLogId = prev.logs[prev.selectedTaskIndex]?.id;
|
|
21636
|
+
const foundIdx = prevLogId ? logs.findIndex((t) => t.id === prevLogId) : -1;
|
|
21637
|
+
const nextIndex = foundIdx >= 0 ? foundIdx : Math.max(0, Math.min(prev.selectedTaskIndex, logs.length - 1));
|
|
21638
|
+
return {
|
|
21639
|
+
...prev,
|
|
21640
|
+
board,
|
|
21641
|
+
logs,
|
|
21642
|
+
error: null,
|
|
21643
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
21644
|
+
selectedTaskIndex: nextIndex,
|
|
21645
|
+
reloadFlash: true,
|
|
21646
|
+
lastContentHash: contentHash
|
|
21647
|
+
};
|
|
21648
|
+
}
|
|
21649
|
+
const prevColumn = prev.board?.columns[prev.activeColumnIndex];
|
|
21650
|
+
const prevTaskId = prevColumn?.tasks[prev.selectedTaskIndex]?.id;
|
|
21651
|
+
let newColumnIndex = prev.activeColumnIndex;
|
|
21652
|
+
let newTaskIndex = 0;
|
|
21653
|
+
if (prevColumn) {
|
|
21654
|
+
const colIdx = board.columns.findIndex((c) => c.id === prevColumn.id);
|
|
21655
|
+
if (colIdx >= 0) {
|
|
21656
|
+
newColumnIndex = colIdx;
|
|
21657
|
+
if (prevTaskId) {
|
|
21658
|
+
const taskIdx = board.columns[colIdx].tasks.findIndex((t) => t.id === prevTaskId);
|
|
21659
|
+
if (taskIdx >= 0) newTaskIndex = taskIdx;
|
|
21660
|
+
}
|
|
21661
|
+
}
|
|
21662
|
+
}
|
|
21663
|
+
newColumnIndex = Math.min(newColumnIndex, Math.max(0, board.columns.length - 1));
|
|
21664
|
+
const col = board.columns[newColumnIndex];
|
|
21665
|
+
newTaskIndex = Math.min(newTaskIndex, Math.max(0, (col?.tasks.length || 1) - 1));
|
|
21666
|
+
return {
|
|
21667
|
+
...prev,
|
|
21668
|
+
board,
|
|
21669
|
+
logs,
|
|
21670
|
+
error: null,
|
|
21671
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
21672
|
+
activeColumnIndex: newColumnIndex,
|
|
21673
|
+
selectedTaskIndex: newTaskIndex,
|
|
21674
|
+
reloadFlash: true,
|
|
21675
|
+
lastContentHash: contentHash
|
|
21676
|
+
};
|
|
21677
|
+
}
|
|
21678
|
+
let errorMessage;
|
|
21679
|
+
if (parseError) {
|
|
21680
|
+
errorMessage = parseError;
|
|
21681
|
+
} else {
|
|
21682
|
+
errorMessage = "Not a valid brainfile";
|
|
21683
|
+
}
|
|
21684
|
+
return {
|
|
21685
|
+
...prev,
|
|
21686
|
+
error: errorMessage,
|
|
21687
|
+
lastContentHash: contentHash
|
|
21688
|
+
};
|
|
21689
|
+
});
|
|
21690
|
+
if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
|
|
21691
|
+
flashTimeoutRef.current = setTimeout(() => {
|
|
21692
|
+
setState((prev) => ({ ...prev, reloadFlash: false }));
|
|
21693
|
+
}, 1e3);
|
|
21694
|
+
} catch (err) {
|
|
21695
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
21696
|
+
let userMessage = errorMsg;
|
|
21697
|
+
if (errorMsg.includes("ENOENT")) {
|
|
21698
|
+
userMessage = `File not found: ${filePath}`;
|
|
21699
|
+
} else if (errorMsg.includes("EACCES")) {
|
|
21700
|
+
userMessage = `Permission denied: ${filePath}`;
|
|
21701
|
+
} else if (errorMsg.includes("EISDIR")) {
|
|
21702
|
+
userMessage = `Path is a directory: ${filePath}`;
|
|
21703
|
+
}
|
|
21704
|
+
setState((prev) => ({
|
|
21705
|
+
...prev,
|
|
21706
|
+
error: userMessage
|
|
21707
|
+
}));
|
|
21708
|
+
}
|
|
21709
|
+
}, [filePath, setState]);
|
|
21710
|
+
(0, import_react34.useEffect)(() => {
|
|
21711
|
+
loadBrainfile();
|
|
21712
|
+
}, [loadBrainfile]);
|
|
21713
|
+
(0, import_react34.useEffect)(() => {
|
|
21714
|
+
return () => {
|
|
21715
|
+
if (flashTimeoutRef.current) clearTimeout(flashTimeoutRef.current);
|
|
21716
|
+
};
|
|
21717
|
+
}, []);
|
|
21718
|
+
(0, import_react34.useEffect)(() => {
|
|
21719
|
+
const v2 = (0, import_core.isV2)(filePath);
|
|
21720
|
+
const dirs = v2 ? (0, import_core.getV2Dirs)(filePath) : null;
|
|
21721
|
+
const watchPaths = v2 && dirs ? [filePath, dirs.boardDir, dirs.logsDir] : [filePath];
|
|
21722
|
+
const watcher = watch(watchPaths, {
|
|
21723
|
+
persistent: true,
|
|
21724
|
+
ignoreInitial: true,
|
|
21725
|
+
usePolling: true,
|
|
21726
|
+
interval: 750,
|
|
21727
|
+
awaitWriteFinish: {
|
|
21728
|
+
stabilityThreshold: 250,
|
|
21729
|
+
pollInterval: 100
|
|
21730
|
+
},
|
|
21731
|
+
depth: v2 ? 1 : 0
|
|
21732
|
+
});
|
|
21733
|
+
watcher.on("add", () => loadBrainfile());
|
|
21734
|
+
watcher.on("change", () => loadBrainfile());
|
|
21735
|
+
watcher.on("unlink", () => loadBrainfile());
|
|
21736
|
+
watcher.on("error", (err) => {
|
|
21737
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21738
|
+
setState((prev) => ({
|
|
21739
|
+
...prev,
|
|
21740
|
+
error: `File watcher error: ${message}`
|
|
21741
|
+
}));
|
|
21742
|
+
});
|
|
21743
|
+
return () => {
|
|
21744
|
+
watcher.close();
|
|
21745
|
+
};
|
|
21746
|
+
}, [filePath, loadBrainfile, setState]);
|
|
21747
|
+
return { loadBrainfile };
|
|
21748
|
+
}
|
|
21749
|
+
|
|
21750
|
+
// src/tui/hooks/useKeyboardNavigation.ts
|
|
21751
|
+
var import_react36 = __toESM(require_react());
|
|
21752
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
21753
|
+
|
|
21754
|
+
// src/tui/detailStops.ts
|
|
21755
|
+
function buildDetailStops(task, children) {
|
|
21756
|
+
const stops = children.map((child) => ({ kind: "child", task: child }));
|
|
21757
|
+
(task.subtasks ?? []).forEach((subtask, index) => {
|
|
21758
|
+
stops.push({ kind: "subtask", subtask, index });
|
|
21759
|
+
});
|
|
21760
|
+
return stops;
|
|
21761
|
+
}
|
|
21762
|
+
function clampDetailCursor(cursor, stopCount) {
|
|
21763
|
+
if (stopCount <= 0) return 0;
|
|
21764
|
+
return Math.max(0, Math.min(cursor, stopCount - 1));
|
|
21765
|
+
}
|
|
21766
|
+
|
|
21767
|
+
// src/tui/components/DetailView.tsx
|
|
21768
|
+
var import_react35 = __toESM(require_react());
|
|
21769
|
+
var MAX_DELIVERABLES = 3;
|
|
21770
|
+
var MAX_VALIDATION_COMMANDS = 2;
|
|
21771
|
+
function computeDetailLayout(task, width, height, children, activity) {
|
|
21772
|
+
const contentWidth = Math.max(20, width - 2);
|
|
21773
|
+
const stacked = contentWidth < 60;
|
|
21774
|
+
const subtasks = task.subtasks ?? [];
|
|
21775
|
+
const files = task.relatedFiles ?? [];
|
|
21776
|
+
const contract = task.contract;
|
|
21777
|
+
const bodyLines = buildBodyLines(task.description, contentWidth);
|
|
21778
|
+
const metaLines = stacked ? 4 : 1;
|
|
21779
|
+
const childrenLines = children.length > 0 ? 1 + children.length : 0;
|
|
21780
|
+
const subtasksLines = subtasks.length > 0 ? 1 + subtasks.length : 0;
|
|
21781
|
+
const contractLines = contract ? 1 + Math.min(contract.deliverables?.length ?? 0, MAX_DELIVERABLES) + Math.min(contract.validation?.commands?.length ?? 0, MAX_VALIDATION_COMMANDS) + (contract.feedback ? 1 : 0) : 0;
|
|
21782
|
+
const activityLines = activity.length > 0 ? 1 + activity.length : 0;
|
|
21783
|
+
const filesLines = files.length > 0 ? 1 : 0;
|
|
21784
|
+
const sectionsWithMargin = [
|
|
21785
|
+
bodyLines.length > 0,
|
|
21786
|
+
childrenLines > 0,
|
|
21787
|
+
subtasksLines > 0,
|
|
21788
|
+
contractLines > 0,
|
|
21789
|
+
activityLines > 0,
|
|
21790
|
+
filesLines > 0
|
|
21791
|
+
].filter(Boolean).length;
|
|
21792
|
+
const bodyBudget = Math.max(
|
|
21793
|
+
2,
|
|
21794
|
+
height - 2 - metaLines - 1 - childrenLines - subtasksLines - contractLines - activityLines - filesLines - sectionsWithMargin
|
|
21795
|
+
);
|
|
21796
|
+
return { bodyLines, bodyBudget, bodyOverflows: bodyLines.length > bodyBudget };
|
|
21797
|
+
}
|
|
21798
|
+
function DetailView({
|
|
21799
|
+
task,
|
|
21800
|
+
columnLabel,
|
|
21801
|
+
width,
|
|
21802
|
+
height,
|
|
21803
|
+
breadcrumb,
|
|
21804
|
+
parent,
|
|
21805
|
+
children,
|
|
21806
|
+
activity,
|
|
21807
|
+
cursor,
|
|
21808
|
+
scrollOffset
|
|
21809
|
+
}) {
|
|
21810
|
+
const contentWidth = Math.max(20, width - 2);
|
|
21811
|
+
const stacked = contentWidth < 60;
|
|
21812
|
+
const priority = task.priority;
|
|
21813
|
+
const subtasks = task.subtasks ?? [];
|
|
21814
|
+
const files = task.relatedFiles ?? [];
|
|
21815
|
+
const contract = task.contract;
|
|
21816
|
+
const { bodyLines, bodyBudget, bodyOverflows } = computeDetailLayout(
|
|
21817
|
+
task,
|
|
21818
|
+
width,
|
|
21819
|
+
height,
|
|
21820
|
+
children,
|
|
21821
|
+
activity
|
|
21822
|
+
);
|
|
21823
|
+
const clampedScroll = Math.max(0, Math.min(scrollOffset, Math.max(0, bodyLines.length - bodyBudget)));
|
|
21824
|
+
const visibleBody = bodyLines.slice(clampedScroll, clampedScroll + bodyBudget);
|
|
21825
|
+
const stateText = priority ? `${columnLabel} \xB7 ${priority}` : columnLabel;
|
|
21826
|
+
const breadcrumbBudget = Math.max(6, Math.floor(contentWidth * 0.4));
|
|
21827
|
+
const breadcrumbStr = truncateStart(breadcrumb.join(" \u25B8 ") || task.id, breadcrumbBudget);
|
|
21828
|
+
const headWidth = Math.max(1, contentWidth - stateText.length - 2);
|
|
21829
|
+
const head = truncate(`${breadcrumbStr} ${task.title}`, headWidth);
|
|
21830
|
+
return /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", width, flexShrink: 0 }, /* @__PURE__ */ import_react35.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.text, bold: true }, head), /* @__PURE__ */ import_react35.default.createElement(Text, null, pad(Math.max(1, contentWidth - head.length - stateText.length))), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textSecondary }, columnLabel), priority ? /* @__PURE__ */ import_react35.default.createElement(Text, null, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textDim }, " \xB7 "), /* @__PURE__ */ import_react35.default.createElement(Text, { color: getPriorityColor(priority) }, priority)) : null)), /* @__PURE__ */ import_react35.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textDim }, RULE.repeat(contentWidth))), /* @__PURE__ */ import_react35.default.createElement(MetadataBlock, { task, parent, stacked, width }), bodyLines.length > 0 ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react35.default.createElement(
|
|
21831
|
+
DescriptionHeading,
|
|
21832
|
+
{
|
|
21833
|
+
bodyOverflows,
|
|
21834
|
+
scrollStart: clampedScroll + 1,
|
|
21835
|
+
total: bodyLines.length,
|
|
21836
|
+
contentWidth
|
|
22107
21837
|
}
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
|
|
21838
|
+
), visibleBody.map((line, index) => /* @__PURE__ */ import_react35.default.createElement(
|
|
21839
|
+
Text,
|
|
21840
|
+
{
|
|
21841
|
+
key: `body-${index}`,
|
|
21842
|
+
wrap: "truncate",
|
|
21843
|
+
bold: line.kind === "heading",
|
|
21844
|
+
color: line.kind === "code" ? PALETTE.textMuted : line.kind === "heading" ? PALETTE.text : PALETTE.textSecondary
|
|
21845
|
+
},
|
|
21846
|
+
line.text || " "
|
|
21847
|
+
))) : null, children.length > 0 ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, `children (${children.length})`), children.map((child, index) => /* @__PURE__ */ import_react35.default.createElement(
|
|
21848
|
+
StopRow,
|
|
21849
|
+
{
|
|
21850
|
+
key: child.id,
|
|
21851
|
+
selected: index === cursor,
|
|
21852
|
+
left: ` ${child.id} ${child.title}`,
|
|
21853
|
+
right: child.column ?? "",
|
|
21854
|
+
rightColor: PALETTE.textSecondary,
|
|
21855
|
+
contentWidth
|
|
21856
|
+
}
|
|
21857
|
+
))) : null, subtasks.length > 0 ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, `subtasks (${subtasks.filter((s) => s.completed).length}/${subtasks.length})`), subtasks.map((subtask, index) => {
|
|
21858
|
+
const stopIndex = children.length + index;
|
|
21859
|
+
const glyph = subtask.completed ? GLYPHS.subtaskDone : GLYPHS.subtaskOpen;
|
|
21860
|
+
return /* @__PURE__ */ import_react35.default.createElement(
|
|
21861
|
+
StopRow,
|
|
21862
|
+
{
|
|
21863
|
+
key: subtask.id,
|
|
21864
|
+
selected: stopIndex === cursor,
|
|
21865
|
+
left: ` ${glyph} ${subtask.title}`,
|
|
21866
|
+
leftColor: subtask.completed ? PALETTE.textMuted : PALETTE.textSecondary,
|
|
21867
|
+
contentWidth
|
|
21868
|
+
}
|
|
21869
|
+
);
|
|
21870
|
+
})) : null, contract ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react35.default.createElement(Text, null, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, "contract "), /* @__PURE__ */ import_react35.default.createElement(Text, { color: getContractStateColor(contract.status) }, contract.status)), contract.deliverables?.length ? /* @__PURE__ */ import_react35.default.createElement(
|
|
21871
|
+
LabeledList,
|
|
21872
|
+
{
|
|
21873
|
+
label: "deliverables",
|
|
21874
|
+
items: contract.deliverables.slice(0, MAX_DELIVERABLES).map((d) => d.description ? `${d.path} \u2014 ${d.description}` : d.path),
|
|
21875
|
+
contentWidth,
|
|
21876
|
+
color: PALETTE.textSecondary
|
|
22111
21877
|
}
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
21878
|
+
) : null, contract.validation?.commands?.length ? /* @__PURE__ */ import_react35.default.createElement(
|
|
21879
|
+
LabeledList,
|
|
21880
|
+
{
|
|
21881
|
+
label: "validation",
|
|
21882
|
+
items: contract.validation.commands.slice(0, MAX_VALIDATION_COMMANDS),
|
|
21883
|
+
contentWidth,
|
|
21884
|
+
color: PALETTE.textMuted
|
|
21885
|
+
}
|
|
21886
|
+
) : null, contract.feedback ? /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, " feedback "), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.error }, truncate(contract.feedback, Math.max(1, contentWidth - 12)))) : null) : null, activity.length > 0 ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, "activity"), activity.map((entry, index) => {
|
|
21887
|
+
const stamp = entry.at ? entry.at.slice(5, 10) : " ";
|
|
21888
|
+
const prefix = ` ${stamp} `;
|
|
21889
|
+
return /* @__PURE__ */ import_react35.default.createElement(Text, { key: `activity-${index}`, wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textDim }, prefix), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textSecondary }, truncate(entry.text, Math.max(1, contentWidth - prefix.length))));
|
|
21890
|
+
})) : null, files.length > 0 ? /* @__PURE__ */ import_react35.default.createElement(Box_default, { paddingLeft: 1, marginTop: 1, width }, /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, "files "), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textSecondary }, truncate(files.join(" \xB7 "), Math.max(1, contentWidth - 7))))) : null);
|
|
21891
|
+
}
|
|
21892
|
+
function DescriptionHeading({
|
|
21893
|
+
bodyOverflows,
|
|
21894
|
+
scrollStart,
|
|
21895
|
+
total,
|
|
21896
|
+
contentWidth
|
|
21897
|
+
}) {
|
|
21898
|
+
const indicator = bodyOverflows ? `${GLYPHS.scroll} ${scrollStart}/${total}` : "";
|
|
21899
|
+
const gap = Math.max(1, contentWidth - "description".length - indicator.length);
|
|
21900
|
+
return /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, "description"), indicator ? /* @__PURE__ */ import_react35.default.createElement(Text, null, pad(gap)) : null, indicator ? /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textDim }, indicator) : null);
|
|
21901
|
+
}
|
|
21902
|
+
function StopRow({
|
|
21903
|
+
selected,
|
|
21904
|
+
left,
|
|
21905
|
+
right,
|
|
21906
|
+
leftColor,
|
|
21907
|
+
rightColor,
|
|
21908
|
+
contentWidth
|
|
21909
|
+
}) {
|
|
21910
|
+
const rightText = right ?? "";
|
|
21911
|
+
const leftTruncated = truncate(left, Math.max(1, contentWidth - rightText.length - 2));
|
|
21912
|
+
const gap = Math.max(1, contentWidth - leftTruncated.length - rightText.length);
|
|
21913
|
+
return /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: selected ? void 0 : leftColor, inverse: selected }, leftTruncated), /* @__PURE__ */ import_react35.default.createElement(Text, null, pad(gap)), rightText ? /* @__PURE__ */ import_react35.default.createElement(Text, { color: rightColor }, rightText) : null);
|
|
21914
|
+
}
|
|
21915
|
+
function LabeledList({
|
|
21916
|
+
label,
|
|
21917
|
+
items,
|
|
21918
|
+
contentWidth,
|
|
21919
|
+
color
|
|
21920
|
+
}) {
|
|
21921
|
+
const labelWidth = 16;
|
|
21922
|
+
return /* @__PURE__ */ import_react35.default.createElement(import_react35.default.Fragment, null, items.map((item, index) => /* @__PURE__ */ import_react35.default.createElement(Text, { key: `${label}-${index}`, wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, ` ${index === 0 ? label.padEnd(labelWidth - 2) : " ".repeat(labelWidth - 2)}`), /* @__PURE__ */ import_react35.default.createElement(Text, { color }, truncate(item, Math.max(1, contentWidth - labelWidth))))));
|
|
21923
|
+
}
|
|
21924
|
+
function MetadataBlock({
|
|
21925
|
+
task,
|
|
21926
|
+
parent,
|
|
21927
|
+
stacked,
|
|
21928
|
+
width
|
|
21929
|
+
}) {
|
|
21930
|
+
const tags = task.tags?.length ? task.tags.map((t) => `#${t}`).join(" ") : "\u2014";
|
|
21931
|
+
const assignee = task.assignee || "\u2014";
|
|
21932
|
+
const created = task.createdAt ? task.createdAt.slice(0, 10) : "\u2014";
|
|
21933
|
+
const parentLabel = parentLabelFor(task, parent);
|
|
21934
|
+
const fields = [
|
|
21935
|
+
["tags", tags],
|
|
21936
|
+
["assignee", assignee],
|
|
21937
|
+
["created", created],
|
|
21938
|
+
["parent", parentLabel]
|
|
21939
|
+
];
|
|
21940
|
+
if (stacked) {
|
|
21941
|
+
return /* @__PURE__ */ import_react35.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, width }, fields.map(([label, value]) => /* @__PURE__ */ import_react35.default.createElement(Text, { key: label, wrap: "truncate" }, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, `${label} `), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textSecondary }, value))));
|
|
22117
21942
|
}
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
21943
|
+
return /* @__PURE__ */ import_react35.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react35.default.createElement(Text, { wrap: "truncate" }, fields.map(([label, value], index) => /* @__PURE__ */ import_react35.default.createElement(Text, { key: label }, index > 0 ? /* @__PURE__ */ import_react35.default.createElement(Text, null, pad(4)) : null, /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textMuted }, `${label} `), /* @__PURE__ */ import_react35.default.createElement(Text, { color: PALETTE.textSecondary }, value)))));
|
|
21944
|
+
}
|
|
21945
|
+
function parentLabelFor(task, parent) {
|
|
21946
|
+
if (!task.parentId) return "\u2014";
|
|
21947
|
+
if (!parent) return `${task.parentId} \u2197`;
|
|
21948
|
+
if (getDocType(parent) === "plan") return `plan ${parent.id}`;
|
|
21949
|
+
return parent.id;
|
|
21950
|
+
}
|
|
21951
|
+
function buildBodyLines(description, width) {
|
|
21952
|
+
if (!description) return [];
|
|
21953
|
+
const out = [];
|
|
21954
|
+
let inFence = false;
|
|
21955
|
+
for (const raw of description.split("\n")) {
|
|
21956
|
+
const line = raw.replace(/\s+$/, "");
|
|
21957
|
+
if (line.trim().startsWith("```")) {
|
|
21958
|
+
inFence = !inFence;
|
|
21959
|
+
continue;
|
|
21960
|
+
}
|
|
21961
|
+
if (inFence) {
|
|
21962
|
+
out.push({ text: ` ${truncate(line, Math.max(1, width - 2))}`, kind: "code" });
|
|
21963
|
+
continue;
|
|
21964
|
+
}
|
|
21965
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
21966
|
+
out.push({ text: truncate(line.replace(/^#{1,6}\s*/, ""), width), kind: "heading" });
|
|
21967
|
+
continue;
|
|
21968
|
+
}
|
|
21969
|
+
if (/^\s*[-*+]\s/.test(line)) {
|
|
21970
|
+
const item = line.replace(/^\s*[-*+]\s*/, "");
|
|
21971
|
+
for (const wrapped of wrapText2(item, Math.max(1, width - 4))) {
|
|
21972
|
+
out.push({ text: ` \u2022 ${wrapped}`, kind: "list" });
|
|
21973
|
+
}
|
|
21974
|
+
continue;
|
|
21975
|
+
}
|
|
21976
|
+
if (line.trim() === "") {
|
|
21977
|
+
out.push({ text: "", kind: "text" });
|
|
21978
|
+
continue;
|
|
21979
|
+
}
|
|
21980
|
+
for (const wrapped of wrapText2(line, width)) {
|
|
21981
|
+
out.push({ text: wrapped, kind: "text" });
|
|
22124
21982
|
}
|
|
22125
|
-
archiveBoard = parseResult.board;
|
|
22126
|
-
} catch (err) {
|
|
22127
|
-
return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
|
|
22128
|
-
}
|
|
22129
|
-
if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
|
|
22130
|
-
return { success: false, error: "Logs are empty" };
|
|
22131
|
-
}
|
|
22132
|
-
const taskIndex = archiveBoard.archive.findIndex((t) => t.id === taskId);
|
|
22133
|
-
if (taskIndex === -1) {
|
|
22134
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
22135
21983
|
}
|
|
22136
|
-
|
|
21984
|
+
return out;
|
|
21985
|
+
}
|
|
21986
|
+
|
|
21987
|
+
// src/tui/tuiState.ts
|
|
21988
|
+
var import_core5 = __toESM(require_dist());
|
|
21989
|
+
import * as fs5 from "fs";
|
|
21990
|
+
import * as path3 from "path";
|
|
21991
|
+
function getTuiStatePath(brainfilePath) {
|
|
21992
|
+
return path3.join((0, import_core5.getBrainfileStateDir)(brainfilePath), import_core5.DOT_BRAINFILE_STATE_DIRNAME, "tui.json");
|
|
21993
|
+
}
|
|
21994
|
+
function readTuiState(brainfilePath) {
|
|
21995
|
+
const statePath = getTuiStatePath(brainfilePath);
|
|
21996
|
+
if (!fs5.existsSync(statePath)) return { collapsed: [] };
|
|
22137
21997
|
try {
|
|
22138
|
-
const
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
|
|
21998
|
+
const parsed = JSON.parse(fs5.readFileSync(statePath, "utf-8"));
|
|
21999
|
+
const collapsed = Array.isArray(parsed.collapsed) ? parsed.collapsed.filter((id) => typeof id === "string") : [];
|
|
22000
|
+
return {
|
|
22001
|
+
collapsed,
|
|
22002
|
+
...typeof parsed.lastColumn === "string" ? { lastColumn: parsed.lastColumn } : {},
|
|
22003
|
+
...typeof parsed.lastTypeFilter === "string" ? { lastTypeFilter: parsed.lastTypeFilter } : {}
|
|
22004
|
+
};
|
|
22005
|
+
} catch {
|
|
22006
|
+
return { collapsed: [] };
|
|
22142
22007
|
}
|
|
22143
|
-
|
|
22008
|
+
}
|
|
22009
|
+
function patchTuiState(brainfilePath, patch) {
|
|
22010
|
+
const current = readTuiState(brainfilePath);
|
|
22011
|
+
writeTuiState(brainfilePath, { ...current, ...patch });
|
|
22012
|
+
}
|
|
22013
|
+
function writeTuiState(brainfilePath, state) {
|
|
22014
|
+
(0, import_core5.ensureDotBrainfileGitignore)(brainfilePath);
|
|
22015
|
+
(0, import_core5.atomicWriteFileSync)(getTuiStatePath(brainfilePath), `${JSON.stringify(state, null, 2)}
|
|
22016
|
+
`);
|
|
22144
22017
|
}
|
|
22145
22018
|
|
|
22146
22019
|
// src/tui/hooks/useKeyboardNavigation.ts
|
|
22147
|
-
var RULE_TYPES = ["always", "never", "prefer", "context"];
|
|
22148
22020
|
var STATUS_MESSAGE_MS = 3e3;
|
|
22149
22021
|
function useKeyboardNavigation({
|
|
22150
22022
|
state,
|
|
@@ -22154,18 +22026,28 @@ function useKeyboardNavigation({
|
|
|
22154
22026
|
viewportHeight,
|
|
22155
22027
|
loadBrainfile,
|
|
22156
22028
|
filePath,
|
|
22157
|
-
allColumns
|
|
22029
|
+
allColumns,
|
|
22030
|
+
typeCycleOptions,
|
|
22031
|
+
detailTask,
|
|
22032
|
+
detailChildren,
|
|
22033
|
+
detailParent,
|
|
22034
|
+
detailActivity,
|
|
22035
|
+
detailWidth,
|
|
22036
|
+
detailHeight,
|
|
22037
|
+
doneView,
|
|
22038
|
+
detailArchived
|
|
22158
22039
|
}) {
|
|
22159
|
-
const { exit } = use_app_default();
|
|
22160
|
-
const
|
|
22161
|
-
(0,
|
|
22040
|
+
const { exit, suspendTerminal } = use_app_default();
|
|
22041
|
+
const { isRawModeSupported } = use_stdin_default();
|
|
22042
|
+
const statusTimers = (0, import_react36.useRef)(/* @__PURE__ */ new Set());
|
|
22043
|
+
(0, import_react36.useEffect)(
|
|
22162
22044
|
() => () => {
|
|
22163
22045
|
for (const timer of statusTimers.current) clearTimeout(timer);
|
|
22164
22046
|
statusTimers.current.clear();
|
|
22165
22047
|
},
|
|
22166
22048
|
[]
|
|
22167
22049
|
);
|
|
22168
|
-
const showStatus = (0,
|
|
22050
|
+
const showStatus = (0, import_react36.useCallback)(
|
|
22169
22051
|
(text, type) => {
|
|
22170
22052
|
const timestamp = Date.now();
|
|
22171
22053
|
setState((prev) => ({ ...prev, statusMessage: { text, type, timestamp } }));
|
|
@@ -22179,14 +22061,56 @@ function useKeyboardNavigation({
|
|
|
22179
22061
|
},
|
|
22180
22062
|
[setState]
|
|
22181
22063
|
);
|
|
22064
|
+
const openInEditor = (0, import_react36.useCallback)(
|
|
22065
|
+
(taskId) => {
|
|
22066
|
+
const located = resolveTaskFilePath(filePath, taskId);
|
|
22067
|
+
if (!located.success) {
|
|
22068
|
+
showStatus(located.error, "error");
|
|
22069
|
+
return;
|
|
22070
|
+
}
|
|
22071
|
+
if (!isRawModeSupported) {
|
|
22072
|
+
showStatus(STATUS.editorNeedsTty, "error");
|
|
22073
|
+
return;
|
|
22074
|
+
}
|
|
22075
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
22076
|
+
void suspendTerminal(async () => {
|
|
22077
|
+
spawnSync2(editor, [located.path], { stdio: "inherit", shell: true });
|
|
22078
|
+
}).then(() => {
|
|
22079
|
+
loadBrainfile(true);
|
|
22080
|
+
}).catch((err) => {
|
|
22081
|
+
showStatus(
|
|
22082
|
+
STATUS.editorFailed(err instanceof Error ? err.message : String(err)),
|
|
22083
|
+
"error"
|
|
22084
|
+
);
|
|
22085
|
+
});
|
|
22086
|
+
},
|
|
22087
|
+
[filePath, showStatus, suspendTerminal, loadBrainfile, isRawModeSupported]
|
|
22088
|
+
);
|
|
22089
|
+
const rememberColumn = (0, import_react36.useCallback)(
|
|
22090
|
+
(columnIndex) => {
|
|
22091
|
+
const id = allColumns[columnIndex]?.id;
|
|
22092
|
+
if (id) patchTuiState(filePath, { lastColumn: id });
|
|
22093
|
+
},
|
|
22094
|
+
[allColumns, filePath]
|
|
22095
|
+
);
|
|
22182
22096
|
const currentTask = rows[state.selectedTaskIndex]?.task;
|
|
22183
22097
|
const maxRowIndex = Math.max(0, rows.length - 1);
|
|
22098
|
+
const overlayTarget = state.detailPath.length > 0 ? detailTask : currentTask;
|
|
22184
22099
|
const setMode = (mode, extra = {}) => setState((prev) => ({ ...prev, mode, ...extra }));
|
|
22100
|
+
const closeOverlay = (opts = {}) => {
|
|
22101
|
+
const clearDetail = opts.docRemoved || state.detailPath.length === 0;
|
|
22102
|
+
setState((prev) => ({
|
|
22103
|
+
...prev,
|
|
22104
|
+
mode: clearDetail ? "browse" : "detail",
|
|
22105
|
+
detailPath: clearDetail ? [] : prev.detailPath,
|
|
22106
|
+
completeConfirm: null
|
|
22107
|
+
}));
|
|
22108
|
+
};
|
|
22185
22109
|
const completeTask = (task, force) => {
|
|
22186
22110
|
const result = archiveTaskAction(filePath, task.id, { force });
|
|
22187
22111
|
if (result.success) {
|
|
22188
|
-
showStatus(
|
|
22189
|
-
|
|
22112
|
+
showStatus(STATUS.completed(task.id), "success");
|
|
22113
|
+
closeOverlay({ docRemoved: true });
|
|
22190
22114
|
loadBrainfile(true);
|
|
22191
22115
|
return;
|
|
22192
22116
|
}
|
|
@@ -22200,12 +22124,30 @@ function useKeyboardNavigation({
|
|
|
22200
22124
|
});
|
|
22201
22125
|
return;
|
|
22202
22126
|
}
|
|
22203
|
-
showStatus(result.error ||
|
|
22204
|
-
|
|
22127
|
+
showStatus(result.error || STATUS.completeFailed, "error");
|
|
22128
|
+
closeOverlay();
|
|
22129
|
+
};
|
|
22130
|
+
const openHelp = () => {
|
|
22131
|
+
setState((prev) => ({
|
|
22132
|
+
...prev,
|
|
22133
|
+
mode: "help",
|
|
22134
|
+
helpReturn: prev.mode === "help" ? prev.helpReturn : prev.mode
|
|
22135
|
+
}));
|
|
22136
|
+
};
|
|
22137
|
+
const closeHelp = () => {
|
|
22138
|
+
setState((prev) => ({
|
|
22139
|
+
...prev,
|
|
22140
|
+
mode: prev.helpReturn && prev.helpReturn !== "help" ? prev.helpReturn : "browse",
|
|
22141
|
+
helpReturn: null
|
|
22142
|
+
}));
|
|
22205
22143
|
};
|
|
22206
22144
|
use_input_default((input, key) => {
|
|
22207
22145
|
if (state.mode === "help") {
|
|
22208
|
-
|
|
22146
|
+
if (input === "q" || key.ctrl && input === "c") {
|
|
22147
|
+
exit();
|
|
22148
|
+
return;
|
|
22149
|
+
}
|
|
22150
|
+
closeHelp();
|
|
22209
22151
|
return;
|
|
22210
22152
|
}
|
|
22211
22153
|
if (state.mode === "complete-confirm") {
|
|
@@ -22214,44 +22156,50 @@ function useKeyboardNavigation({
|
|
|
22214
22156
|
if (target) {
|
|
22215
22157
|
const result = archiveTaskAction(filePath, target.id, { force: true });
|
|
22216
22158
|
if (result.success) {
|
|
22217
|
-
showStatus(
|
|
22159
|
+
showStatus(STATUS.completed(target.id), "success");
|
|
22218
22160
|
loadBrainfile(true);
|
|
22161
|
+
closeOverlay({ docRemoved: true });
|
|
22219
22162
|
} else {
|
|
22220
|
-
showStatus(result.error ||
|
|
22163
|
+
showStatus(result.error || STATUS.completeFailed, "error");
|
|
22164
|
+
closeOverlay();
|
|
22221
22165
|
}
|
|
22166
|
+
} else {
|
|
22167
|
+
closeOverlay();
|
|
22222
22168
|
}
|
|
22223
|
-
setMode("browse", { completeConfirm: null });
|
|
22224
22169
|
return;
|
|
22225
22170
|
}
|
|
22226
22171
|
if (input === "n" || input === "N" || key.escape) {
|
|
22227
|
-
|
|
22228
|
-
showStatus(
|
|
22172
|
+
closeOverlay();
|
|
22173
|
+
showStatus(STATUS.completeCancelled, "info");
|
|
22229
22174
|
}
|
|
22230
22175
|
return;
|
|
22231
22176
|
}
|
|
22232
22177
|
if (state.mode === "delete-confirm") {
|
|
22233
22178
|
if (input === "y" || input === "Y") {
|
|
22234
|
-
if (
|
|
22235
|
-
const result = deleteTaskAction(filePath,
|
|
22179
|
+
if (overlayTarget) {
|
|
22180
|
+
const result = deleteTaskAction(filePath, overlayTarget.id);
|
|
22236
22181
|
if (result.success) {
|
|
22237
|
-
showStatus(
|
|
22182
|
+
showStatus(STATUS.deleted(overlayTarget.id), "success");
|
|
22238
22183
|
loadBrainfile(true);
|
|
22184
|
+
closeOverlay({ docRemoved: true });
|
|
22239
22185
|
} else {
|
|
22240
|
-
showStatus(result.error ||
|
|
22186
|
+
showStatus(result.error || STATUS.deleteFailed, "error");
|
|
22187
|
+
closeOverlay();
|
|
22241
22188
|
}
|
|
22189
|
+
} else {
|
|
22190
|
+
closeOverlay();
|
|
22242
22191
|
}
|
|
22243
|
-
setMode("browse");
|
|
22244
22192
|
return;
|
|
22245
22193
|
}
|
|
22246
22194
|
if (input === "n" || input === "N" || key.escape) {
|
|
22247
|
-
|
|
22248
|
-
showStatus(
|
|
22195
|
+
closeOverlay();
|
|
22196
|
+
showStatus(STATUS.deleteCancelled, "info");
|
|
22249
22197
|
}
|
|
22250
22198
|
return;
|
|
22251
22199
|
}
|
|
22252
22200
|
if (state.mode === "move") {
|
|
22253
22201
|
if (key.escape) {
|
|
22254
|
-
|
|
22202
|
+
closeOverlay();
|
|
22255
22203
|
return;
|
|
22256
22204
|
}
|
|
22257
22205
|
if (key.upArrow || key.leftArrow || input === "k" || input === "h") {
|
|
@@ -22267,16 +22215,16 @@ function useKeyboardNavigation({
|
|
|
22267
22215
|
}
|
|
22268
22216
|
if (key.return) {
|
|
22269
22217
|
const targetColumn = allColumns[state.moveTargetIndex];
|
|
22270
|
-
if (
|
|
22271
|
-
const result = moveTaskAction(filePath,
|
|
22218
|
+
if (overlayTarget && targetColumn) {
|
|
22219
|
+
const result = moveTaskAction(filePath, overlayTarget.id, targetColumn.id);
|
|
22272
22220
|
if (result.success) {
|
|
22273
|
-
showStatus(result.message ||
|
|
22221
|
+
showStatus(result.message || STATUS.moved(targetColumn.title), "success");
|
|
22274
22222
|
loadBrainfile(true);
|
|
22275
22223
|
} else {
|
|
22276
|
-
showStatus(result.error ||
|
|
22224
|
+
showStatus(result.error || STATUS.moveFailed, "error");
|
|
22277
22225
|
}
|
|
22278
22226
|
}
|
|
22279
|
-
|
|
22227
|
+
closeOverlay();
|
|
22280
22228
|
return;
|
|
22281
22229
|
}
|
|
22282
22230
|
const num = Number.parseInt(input, 10);
|
|
@@ -22287,26 +22235,30 @@ function useKeyboardNavigation({
|
|
|
22287
22235
|
}
|
|
22288
22236
|
if (state.mode === "add") {
|
|
22289
22237
|
if (key.escape) {
|
|
22290
|
-
setMode("browse", { newTaskTitle: "" });
|
|
22238
|
+
setMode("browse", { newTaskTitle: "", addThenEdit: false });
|
|
22291
22239
|
return;
|
|
22292
22240
|
}
|
|
22293
22241
|
if (key.return) {
|
|
22294
22242
|
const title = state.newTaskTitle.trim();
|
|
22295
|
-
if (title) {
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
|
|
22299
|
-
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22243
|
+
if (!title) {
|
|
22244
|
+
showStatus(STATUS.titleRequired, "error");
|
|
22245
|
+
return;
|
|
22246
|
+
}
|
|
22247
|
+
const column = allColumns[state.activeColumnIndex];
|
|
22248
|
+
if (column) {
|
|
22249
|
+
const result = addTaskAction(filePath, column.id, { title });
|
|
22250
|
+
if (result.success) {
|
|
22251
|
+
showStatus(result.message || STATUS.addedGeneric, "success");
|
|
22252
|
+
loadBrainfile(true);
|
|
22253
|
+
if (state.addThenEdit) {
|
|
22254
|
+
const newId = result.message?.replace(/^Added\s+/, "").trim();
|
|
22255
|
+
if (newId) openInEditor(newId);
|
|
22304
22256
|
}
|
|
22257
|
+
} else {
|
|
22258
|
+
showStatus(result.error || STATUS.addFailed, "error");
|
|
22305
22259
|
}
|
|
22306
|
-
} else {
|
|
22307
|
-
showStatus("Title required", "error");
|
|
22308
22260
|
}
|
|
22309
|
-
setMode("browse", { newTaskTitle: "" });
|
|
22261
|
+
setMode("browse", { newTaskTitle: "", addThenEdit: false });
|
|
22310
22262
|
return;
|
|
22311
22263
|
}
|
|
22312
22264
|
if (key.backspace || key.delete) {
|
|
@@ -22345,201 +22297,117 @@ function useKeyboardNavigation({
|
|
|
22345
22297
|
return;
|
|
22346
22298
|
}
|
|
22347
22299
|
if (state.mode === "detail") {
|
|
22348
|
-
const
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22300
|
+
const stops = detailTask ? buildDetailStops(detailTask, detailChildren) : [];
|
|
22301
|
+
const cursor = clampDetailCursor(state.detailCursor, stops.length);
|
|
22302
|
+
const stop = stops[cursor];
|
|
22303
|
+
const layout = detailTask ? computeDetailLayout(detailTask, detailWidth, detailHeight, detailChildren, detailActivity) : null;
|
|
22304
|
+
if (input === "q") {
|
|
22305
|
+
exit();
|
|
22306
|
+
return;
|
|
22307
|
+
}
|
|
22308
|
+
if (key.escape) {
|
|
22309
|
+
if (state.detailPath.length > 1) {
|
|
22310
|
+
setState((prev) => ({
|
|
22311
|
+
...prev,
|
|
22312
|
+
detailPath: prev.detailPath.slice(0, -1),
|
|
22313
|
+
detailCursor: 0,
|
|
22314
|
+
detailScroll: 0
|
|
22315
|
+
}));
|
|
22316
|
+
} else {
|
|
22317
|
+
setMode("browse", { detailPath: [] });
|
|
22353
22318
|
}
|
|
22354
|
-
setMode("browse");
|
|
22355
22319
|
return;
|
|
22356
22320
|
}
|
|
22357
22321
|
if (input === "?") {
|
|
22358
|
-
|
|
22322
|
+
openHelp();
|
|
22359
22323
|
return;
|
|
22360
22324
|
}
|
|
22361
|
-
if (input === "
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
const
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
}
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22325
|
+
if (input === "d") {
|
|
22326
|
+
if (layout) {
|
|
22327
|
+
const maxScroll = Math.max(0, layout.bodyLines.length - layout.bodyBudget);
|
|
22328
|
+
const step = Math.max(1, Math.floor(layout.bodyBudget / 2));
|
|
22329
|
+
setState((prev) => ({
|
|
22330
|
+
...prev,
|
|
22331
|
+
detailScroll: Math.min(maxScroll, prev.detailScroll + step)
|
|
22332
|
+
}));
|
|
22333
|
+
}
|
|
22334
|
+
return;
|
|
22335
|
+
}
|
|
22336
|
+
if (input === "u") {
|
|
22337
|
+
if (layout) {
|
|
22338
|
+
const step = Math.max(1, Math.floor(layout.bodyBudget / 2));
|
|
22339
|
+
setState((prev) => ({ ...prev, detailScroll: Math.max(0, prev.detailScroll - step) }));
|
|
22373
22340
|
}
|
|
22374
22341
|
return;
|
|
22375
22342
|
}
|
|
22376
22343
|
if (key.downArrow || input === "j") {
|
|
22377
22344
|
setState((prev) => ({
|
|
22378
22345
|
...prev,
|
|
22379
|
-
|
|
22346
|
+
detailCursor: Math.min(clampDetailCursor(prev.detailCursor, stops.length) + 1, Math.max(0, stops.length - 1))
|
|
22380
22347
|
}));
|
|
22381
22348
|
return;
|
|
22382
22349
|
}
|
|
22383
22350
|
if (key.upArrow || input === "k") {
|
|
22384
22351
|
setState((prev) => ({
|
|
22385
22352
|
...prev,
|
|
22386
|
-
|
|
22353
|
+
detailCursor: Math.max(0, clampDetailCursor(prev.detailCursor, stops.length) - 1)
|
|
22387
22354
|
}));
|
|
22388
22355
|
return;
|
|
22389
22356
|
}
|
|
22390
|
-
if (
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
if (input === "e" && currentTask) {
|
|
22399
|
-
const result = editTaskInEditor(filePath, currentTask.id);
|
|
22400
|
-
if (result.success) {
|
|
22401
|
-
showStatus(result.message || "Task updated", "success");
|
|
22402
|
-
loadBrainfile(true);
|
|
22403
|
-
} else {
|
|
22404
|
-
showStatus(result.error || "Edit failed", "error");
|
|
22405
|
-
}
|
|
22406
|
-
return;
|
|
22407
|
-
}
|
|
22408
|
-
if (input === "p" && currentTask) {
|
|
22409
|
-
const result = cyclePriorityAction(filePath, currentTask.id);
|
|
22410
|
-
if (result.success) {
|
|
22411
|
-
showStatus(result.message || "Priority updated", "success");
|
|
22412
|
-
loadBrainfile(true);
|
|
22413
|
-
} else {
|
|
22414
|
-
showStatus(result.error || "Failed to update priority", "error");
|
|
22357
|
+
if (key.return) {
|
|
22358
|
+
if (stop?.kind === "child") {
|
|
22359
|
+
setState((prev) => ({
|
|
22360
|
+
...prev,
|
|
22361
|
+
detailPath: [...prev.detailPath, stop.task.id],
|
|
22362
|
+
detailCursor: 0,
|
|
22363
|
+
detailScroll: 0
|
|
22364
|
+
}));
|
|
22415
22365
|
}
|
|
22416
|
-
}
|
|
22417
|
-
return;
|
|
22418
|
-
}
|
|
22419
|
-
if (state.mode === "rule-add" || state.mode === "rule-edit") {
|
|
22420
|
-
if (key.escape) {
|
|
22421
|
-
setMode("browse", { ruleEditText: "", ruleEditId: null });
|
|
22422
22366
|
return;
|
|
22423
22367
|
}
|
|
22424
|
-
if (
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
showStatus("Rule text required", "error");
|
|
22368
|
+
if (input === " ") {
|
|
22369
|
+
if (detailArchived) {
|
|
22370
|
+
showStatus(STATUS.readOnly, "info");
|
|
22428
22371
|
return;
|
|
22429
22372
|
}
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
showStatus(result.message || "Rule saved", "success");
|
|
22433
|
-
loadBrainfile(true);
|
|
22434
|
-
} else {
|
|
22435
|
-
showStatus(result.error || "Failed to save rule", "error");
|
|
22436
|
-
}
|
|
22437
|
-
setMode("browse", { ruleEditText: "", ruleEditId: null });
|
|
22438
|
-
return;
|
|
22439
|
-
}
|
|
22440
|
-
if (key.backspace || key.delete) {
|
|
22441
|
-
setState((prev) => ({ ...prev, ruleEditText: prev.ruleEditText.slice(0, -1) }));
|
|
22442
|
-
return;
|
|
22443
|
-
}
|
|
22444
|
-
if (input && !key.ctrl && !key.meta) {
|
|
22445
|
-
setState((prev) => ({ ...prev, ruleEditText: prev.ruleEditText + input }));
|
|
22446
|
-
}
|
|
22447
|
-
return;
|
|
22448
|
-
}
|
|
22449
|
-
if (state.mode === "rule-delete-confirm") {
|
|
22450
|
-
const rules = state.board?.rules?.[state.activeRuleType] || [];
|
|
22451
|
-
const rule = rules[state.selectedRuleIndex];
|
|
22452
|
-
if (input === "y" || input === "Y") {
|
|
22453
|
-
if (rule) {
|
|
22454
|
-
const result = deleteRuleAction(filePath, state.activeRuleType, rule.id);
|
|
22373
|
+
if (stop?.kind === "subtask" && detailTask) {
|
|
22374
|
+
const result = toggleSubtaskAction(filePath, detailTask.id, stop.subtask.id);
|
|
22455
22375
|
if (result.success) {
|
|
22456
|
-
showStatus(
|
|
22376
|
+
showStatus(STATUS.toggled(stop.subtask.id), "success");
|
|
22457
22377
|
loadBrainfile(true);
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
mode: "browse",
|
|
22461
|
-
selectedRuleIndex: Math.max(0, prev.selectedRuleIndex - 1)
|
|
22462
|
-
}));
|
|
22463
|
-
return;
|
|
22378
|
+
} else {
|
|
22379
|
+
showStatus(result.error || STATUS.toggleFailed, "error");
|
|
22464
22380
|
}
|
|
22465
|
-
|
|
22381
|
+
} else {
|
|
22382
|
+
showStatus(STATUS.noSubtasks, "info");
|
|
22466
22383
|
}
|
|
22467
|
-
setMode("browse");
|
|
22468
22384
|
return;
|
|
22469
22385
|
}
|
|
22470
|
-
if (input === "
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
|
|
22386
|
+
if (input === "p") {
|
|
22387
|
+
if (!detailTask?.parentId || !detailParent) {
|
|
22388
|
+
showStatus(STATUS.noParent, "info");
|
|
22389
|
+
return;
|
|
22390
|
+
}
|
|
22391
|
+
const parentId = detailParent.id;
|
|
22392
|
+
setState((prev) => {
|
|
22393
|
+
const stack = prev.detailPath;
|
|
22394
|
+
const nextPath = stack.length > 1 && stack[stack.length - 2] === parentId ? stack.slice(0, -1) : [...stack.slice(0, -1), parentId];
|
|
22395
|
+
return { ...prev, detailPath: nextPath, detailCursor: 0, detailScroll: 0 };
|
|
22396
|
+
});
|
|
22479
22397
|
return;
|
|
22480
22398
|
}
|
|
22481
|
-
if (
|
|
22482
|
-
setState((prev) => ({
|
|
22483
|
-
...prev,
|
|
22484
|
-
logRestoreColumnIndex: Math.min(prev.logRestoreColumnIndex + 1, allColumns.length - 1)
|
|
22485
|
-
}));
|
|
22399
|
+
if (input === "m" && detailTask && !detailArchived) {
|
|
22400
|
+
setState((prev) => ({ ...prev, mode: "move", moveTargetIndex: prev.activeColumnIndex }));
|
|
22486
22401
|
return;
|
|
22487
22402
|
}
|
|
22488
|
-
if (
|
|
22489
|
-
|
|
22490
|
-
...prev,
|
|
22491
|
-
logRestoreColumnIndex: Math.max(prev.logRestoreColumnIndex - 1, 0)
|
|
22492
|
-
}));
|
|
22403
|
+
if (input === "c" && detailTask && !detailArchived && isCompletable(detailTask)) {
|
|
22404
|
+
completeTask(detailTask, false);
|
|
22493
22405
|
return;
|
|
22494
22406
|
}
|
|
22495
|
-
if (
|
|
22496
|
-
|
|
22497
|
-
const column = allColumns[state.logRestoreColumnIndex];
|
|
22498
|
-
if (task && column) {
|
|
22499
|
-
const result = restoreTaskAction(filePath, task.id, column.id);
|
|
22500
|
-
if (result.success) {
|
|
22501
|
-
showStatus(result.message || "Task restored", "success");
|
|
22502
|
-
loadBrainfile(true);
|
|
22503
|
-
const logResult = loadLogs(filePath);
|
|
22504
|
-
setState((prev) => ({
|
|
22505
|
-
...prev,
|
|
22506
|
-
mode: "browse",
|
|
22507
|
-
logs: logResult.logs,
|
|
22508
|
-
selectedLogIndex: Math.max(0, prev.selectedLogIndex - 1)
|
|
22509
|
-
}));
|
|
22510
|
-
return;
|
|
22511
|
-
}
|
|
22512
|
-
showStatus(result.error || "Failed to restore task", "error");
|
|
22513
|
-
}
|
|
22514
|
-
setMode("browse");
|
|
22515
|
-
}
|
|
22516
|
-
return;
|
|
22517
|
-
}
|
|
22518
|
-
if (state.mode === "logs-delete-confirm") {
|
|
22519
|
-
const task = state.logs[state.selectedLogIndex];
|
|
22520
|
-
if (input === "y" || input === "Y") {
|
|
22521
|
-
if (task) {
|
|
22522
|
-
const result = deleteArchivedTaskAction(filePath, task.id);
|
|
22523
|
-
if (result.success) {
|
|
22524
|
-
showStatus(result.message || "Task permanently deleted", "success");
|
|
22525
|
-
const logResult = loadLogs(filePath);
|
|
22526
|
-
setState((prev) => ({
|
|
22527
|
-
...prev,
|
|
22528
|
-
mode: "browse",
|
|
22529
|
-
logs: logResult.logs,
|
|
22530
|
-
selectedLogIndex: Math.max(0, prev.selectedLogIndex - 1)
|
|
22531
|
-
}));
|
|
22532
|
-
return;
|
|
22533
|
-
}
|
|
22534
|
-
showStatus(result.error || "Failed to delete task", "error");
|
|
22535
|
-
}
|
|
22536
|
-
setMode("browse");
|
|
22407
|
+
if (input === "e" && detailTask) {
|
|
22408
|
+
openInEditor(detailTask.id);
|
|
22537
22409
|
return;
|
|
22538
22410
|
}
|
|
22539
|
-
if (input === "n" || input === "N" || key.escape) {
|
|
22540
|
-
setMode("browse");
|
|
22541
|
-
showStatus("Delete cancelled", "info");
|
|
22542
|
-
}
|
|
22543
22411
|
return;
|
|
22544
22412
|
}
|
|
22545
22413
|
if (input === "q" || key.ctrl && input === "c") {
|
|
@@ -22547,317 +22415,238 @@ function useKeyboardNavigation({
|
|
|
22547
22415
|
return;
|
|
22548
22416
|
}
|
|
22549
22417
|
if (input === "?") {
|
|
22550
|
-
|
|
22418
|
+
openHelp();
|
|
22551
22419
|
return;
|
|
22552
22420
|
}
|
|
22553
|
-
if (input === "
|
|
22554
|
-
|
|
22421
|
+
if (input === "/") {
|
|
22422
|
+
setMode("filter", { filterQuery: "", selectedTaskIndex: 0 });
|
|
22555
22423
|
return;
|
|
22556
22424
|
}
|
|
22557
|
-
if (input === "
|
|
22558
|
-
setState((prev) => ({ ...prev,
|
|
22425
|
+
if (input === "L") {
|
|
22426
|
+
setState((prev) => ({ ...prev, doneView: !prev.doneView, selectedTaskIndex: 0 }));
|
|
22559
22427
|
return;
|
|
22560
22428
|
}
|
|
22561
|
-
if (input === "
|
|
22562
|
-
|
|
22429
|
+
if (input === "t") {
|
|
22430
|
+
if (typeCycleOptions.length > 1) {
|
|
22431
|
+
const idx = typeCycleOptions.indexOf(state.activeTypeFilter);
|
|
22432
|
+
const next = typeCycleOptions[(idx < 0 ? 0 : idx + 1) % typeCycleOptions.length];
|
|
22433
|
+
patchTuiState(filePath, { lastTypeFilter: next });
|
|
22434
|
+
setState((prev) => ({ ...prev, activeTypeFilter: next, selectedTaskIndex: 0 }));
|
|
22435
|
+
}
|
|
22563
22436
|
return;
|
|
22564
22437
|
}
|
|
22565
|
-
if (
|
|
22566
|
-
if (
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
else showStatus("No document selected", "info");
|
|
22578
|
-
return;
|
|
22579
|
-
}
|
|
22580
|
-
if (input === "a" || input === "n") {
|
|
22581
|
-
setMode("add", { newTaskTitle: "" });
|
|
22582
|
-
return;
|
|
22583
|
-
}
|
|
22584
|
-
if (input === "m") {
|
|
22585
|
-
if (!currentTask) {
|
|
22586
|
-
showStatus("No document selected", "error");
|
|
22587
|
-
return;
|
|
22588
|
-
}
|
|
22589
|
-
setState((prev) => ({ ...prev, mode: "move", moveTargetIndex: prev.activeColumnIndex }));
|
|
22590
|
-
return;
|
|
22591
|
-
}
|
|
22592
|
-
if (input === "c") {
|
|
22593
|
-
if (!currentTask) {
|
|
22594
|
-
showStatus("No document selected", "error");
|
|
22595
|
-
return;
|
|
22596
|
-
}
|
|
22597
|
-
if (!isCompletable(currentTask)) {
|
|
22598
|
-
showStatus(`${currentTask.id} cannot be completed`, "info");
|
|
22599
|
-
return;
|
|
22600
|
-
}
|
|
22601
|
-
completeTask(currentTask, false);
|
|
22602
|
-
return;
|
|
22603
|
-
}
|
|
22604
|
-
if (input === "d") {
|
|
22605
|
-
if (!currentTask) {
|
|
22606
|
-
showStatus("No document selected", "error");
|
|
22607
|
-
return;
|
|
22608
|
-
}
|
|
22609
|
-
setMode("delete-confirm");
|
|
22610
|
-
return;
|
|
22611
|
-
}
|
|
22612
|
-
if (input === "e") {
|
|
22613
|
-
if (!currentTask) {
|
|
22614
|
-
showStatus("No document selected", "error");
|
|
22615
|
-
return;
|
|
22616
|
-
}
|
|
22617
|
-
const result = editTaskInEditor(filePath, currentTask.id);
|
|
22618
|
-
if (result.success) {
|
|
22619
|
-
showStatus(result.message || "Task updated", "success");
|
|
22620
|
-
loadBrainfile(true);
|
|
22621
|
-
} else {
|
|
22622
|
-
showStatus(result.error || "Edit failed", "error");
|
|
22623
|
-
}
|
|
22624
|
-
return;
|
|
22625
|
-
}
|
|
22626
|
-
if (input === "p") {
|
|
22627
|
-
if (!currentTask) return;
|
|
22628
|
-
const result = cyclePriorityAction(filePath, currentTask.id);
|
|
22629
|
-
if (result.success) {
|
|
22630
|
-
showStatus(result.message || "Priority updated", "success");
|
|
22631
|
-
loadBrainfile(true);
|
|
22632
|
-
} else {
|
|
22633
|
-
showStatus(result.error || "Failed to update priority", "error");
|
|
22634
|
-
}
|
|
22635
|
-
return;
|
|
22636
|
-
}
|
|
22637
|
-
if (input === "y") {
|
|
22638
|
-
if (!currentTask) return;
|
|
22639
|
-
const result = copyToClipboard(currentTask.id);
|
|
22640
|
-
showStatus(
|
|
22641
|
-
result.success ? `Copied ${currentTask.id}` : result.error || "Copy failed",
|
|
22642
|
-
result.success ? "success" : "error"
|
|
22643
|
-
);
|
|
22644
|
-
return;
|
|
22645
|
-
}
|
|
22646
|
-
if (input === "N") {
|
|
22647
|
-
const column = allColumns[state.activeColumnIndex];
|
|
22648
|
-
if (column) {
|
|
22649
|
-
const result = newTaskInEditor(filePath, column.id);
|
|
22650
|
-
if (result.success) {
|
|
22651
|
-
showStatus(result.message || "Task created", "success");
|
|
22652
|
-
loadBrainfile(true);
|
|
22653
|
-
} else {
|
|
22654
|
-
showStatus(result.error || "Failed to create task", "error");
|
|
22655
|
-
}
|
|
22656
|
-
}
|
|
22657
|
-
return;
|
|
22658
|
-
}
|
|
22659
|
-
if (input === "A") {
|
|
22660
|
-
if (!currentTask) return;
|
|
22661
|
-
showStatus("Moving to logs...", "info");
|
|
22662
|
-
archiveTaskActionAsync(filePath, currentTask.id).then((result) => {
|
|
22663
|
-
showStatus(
|
|
22664
|
-
result.success ? result.message || "Task moved to logs" : result.error || "Move to logs failed",
|
|
22665
|
-
result.success ? "success" : "error"
|
|
22666
|
-
);
|
|
22667
|
-
if (result.success) loadBrainfile(true);
|
|
22668
|
-
}).catch((err) => showStatus(`Move to logs failed: ${err}`, "error"));
|
|
22669
|
-
return;
|
|
22670
|
-
}
|
|
22671
|
-
if (key.downArrow || input === "j") {
|
|
22672
|
-
setState((prev) => ({
|
|
22673
|
-
...prev,
|
|
22674
|
-
selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxRowIndex)
|
|
22675
|
-
}));
|
|
22676
|
-
return;
|
|
22677
|
-
}
|
|
22678
|
-
if (key.upArrow || input === "k") {
|
|
22679
|
-
setState((prev) => ({
|
|
22680
|
-
...prev,
|
|
22681
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0)
|
|
22682
|
-
}));
|
|
22683
|
-
return;
|
|
22684
|
-
}
|
|
22685
|
-
if (key.ctrl && input === "d") {
|
|
22686
|
-
setState((prev) => ({
|
|
22687
|
-
...prev,
|
|
22688
|
-
selectedTaskIndex: Math.min(
|
|
22689
|
-
prev.selectedTaskIndex + Math.floor(viewportHeight / 2),
|
|
22690
|
-
maxRowIndex
|
|
22691
|
-
)
|
|
22692
|
-
}));
|
|
22693
|
-
return;
|
|
22694
|
-
}
|
|
22695
|
-
if (key.ctrl && input === "u") {
|
|
22696
|
-
setState((prev) => ({
|
|
22697
|
-
...prev,
|
|
22698
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0)
|
|
22699
|
-
}));
|
|
22700
|
-
return;
|
|
22701
|
-
}
|
|
22702
|
-
if (input === "g") {
|
|
22703
|
-
setState((prev) => ({ ...prev, selectedTaskIndex: 0 }));
|
|
22704
|
-
return;
|
|
22705
|
-
}
|
|
22706
|
-
if (input === "G") {
|
|
22707
|
-
setState((prev) => ({ ...prev, selectedTaskIndex: maxRowIndex }));
|
|
22708
|
-
return;
|
|
22709
|
-
}
|
|
22710
|
-
if (key.tab || key.rightArrow || input === "l" || input === "]") {
|
|
22711
|
-
if (filteredColumnsLength > 0) {
|
|
22712
|
-
setState((prev) => ({
|
|
22713
|
-
...prev,
|
|
22714
|
-
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumnsLength,
|
|
22715
|
-
selectedTaskIndex: 0
|
|
22716
|
-
}));
|
|
22717
|
-
}
|
|
22718
|
-
return;
|
|
22719
|
-
}
|
|
22720
|
-
if (key.shift && key.tab || key.leftArrow || input === "h" || input === "[") {
|
|
22721
|
-
if (filteredColumnsLength > 0) {
|
|
22722
|
-
setState((prev) => ({
|
|
22723
|
-
...prev,
|
|
22724
|
-
activeColumnIndex: prev.activeColumnIndex === 0 ? filteredColumnsLength - 1 : prev.activeColumnIndex - 1,
|
|
22725
|
-
selectedTaskIndex: 0
|
|
22726
|
-
}));
|
|
22727
|
-
}
|
|
22728
|
-
return;
|
|
22729
|
-
}
|
|
22730
|
-
if (key.escape) {
|
|
22731
|
-
setState((prev) => ({ ...prev, filterQuery: "", selectedTaskIndex: 0 }));
|
|
22438
|
+
if (input === " ") {
|
|
22439
|
+
if (doneView) return;
|
|
22440
|
+
const row = rows[state.selectedTaskIndex];
|
|
22441
|
+
if (row && (row.childCount ?? 0) > 0) {
|
|
22442
|
+
const id = row.task.id;
|
|
22443
|
+
setState((prev) => {
|
|
22444
|
+
const next = new Set(prev.collapsedIds);
|
|
22445
|
+
if (next.has(id)) next.delete(id);
|
|
22446
|
+
else next.add(id);
|
|
22447
|
+
patchTuiState(filePath, { collapsed: Array.from(next) });
|
|
22448
|
+
return { ...prev, collapsedIds: next };
|
|
22449
|
+
});
|
|
22732
22450
|
}
|
|
22733
22451
|
return;
|
|
22734
22452
|
}
|
|
22735
|
-
if (
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
return;
|
|
22746
|
-
}
|
|
22747
|
-
if (key.tab || key.rightArrow || input === "l") {
|
|
22748
|
-
const idx = RULE_TYPES.indexOf(state.activeRuleType);
|
|
22749
|
-
setState((prev) => ({
|
|
22750
|
-
...prev,
|
|
22751
|
-
activeRuleType: RULE_TYPES[(idx + 1) % RULE_TYPES.length],
|
|
22752
|
-
selectedRuleIndex: 0
|
|
22753
|
-
}));
|
|
22754
|
-
return;
|
|
22453
|
+
if (input === "r") {
|
|
22454
|
+
loadBrainfile(true);
|
|
22455
|
+
showStatus(STATUS.reloaded, "info");
|
|
22456
|
+
return;
|
|
22457
|
+
}
|
|
22458
|
+
if (key.return) {
|
|
22459
|
+
if (currentTask) {
|
|
22460
|
+
setMode("detail", { detailPath: [currentTask.id], detailCursor: 0, detailScroll: 0 });
|
|
22461
|
+
} else {
|
|
22462
|
+
showStatus(STATUS.noDocument, "info");
|
|
22755
22463
|
}
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22464
|
+
return;
|
|
22465
|
+
}
|
|
22466
|
+
if (input === "a" || input === "n") {
|
|
22467
|
+
if (doneView) {
|
|
22468
|
+
showStatus(STATUS.readOnly, "info");
|
|
22761
22469
|
return;
|
|
22762
22470
|
}
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22471
|
+
setMode("add", { newTaskTitle: "", addThenEdit: false });
|
|
22472
|
+
return;
|
|
22473
|
+
}
|
|
22474
|
+
if (input === "m") {
|
|
22475
|
+
if (doneView) {
|
|
22476
|
+
showStatus(STATUS.readOnly, "info");
|
|
22768
22477
|
return;
|
|
22769
22478
|
}
|
|
22770
|
-
if (
|
|
22771
|
-
|
|
22479
|
+
if (!currentTask) {
|
|
22480
|
+
showStatus(STATUS.noDocument, "error");
|
|
22772
22481
|
return;
|
|
22773
22482
|
}
|
|
22774
|
-
|
|
22775
|
-
|
|
22776
|
-
|
|
22777
|
-
|
|
22483
|
+
setState((prev) => ({ ...prev, mode: "move", moveTargetIndex: prev.activeColumnIndex }));
|
|
22484
|
+
return;
|
|
22485
|
+
}
|
|
22486
|
+
if (input === "c") {
|
|
22487
|
+
if (doneView) {
|
|
22488
|
+
showStatus(STATUS.alreadyCompleted, "info");
|
|
22778
22489
|
return;
|
|
22779
22490
|
}
|
|
22780
|
-
if (
|
|
22781
|
-
|
|
22782
|
-
else showStatus("No rule selected", "error");
|
|
22491
|
+
if (!currentTask) {
|
|
22492
|
+
showStatus(STATUS.noDocument, "error");
|
|
22783
22493
|
return;
|
|
22784
22494
|
}
|
|
22785
|
-
if (
|
|
22786
|
-
|
|
22495
|
+
if (!isCompletable(currentTask)) {
|
|
22496
|
+
showStatus(STATUS.cannotComplete(currentTask.id), "info");
|
|
22787
22497
|
return;
|
|
22788
22498
|
}
|
|
22789
|
-
|
|
22790
|
-
setState((prev) => ({ ...prev, selectedRuleIndex: maxRuleIndex }));
|
|
22791
|
-
}
|
|
22499
|
+
completeTask(currentTask, false);
|
|
22792
22500
|
return;
|
|
22793
|
-
}
|
|
22794
|
-
if (
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
const result = loadLogs(filePath);
|
|
22798
|
-
setState((prev) => ({ ...prev, logs: result.logs }));
|
|
22799
|
-
showStatus("Logs refreshed", "info");
|
|
22800
|
-
return;
|
|
22801
|
-
}
|
|
22802
|
-
if (key.downArrow || input === "j") {
|
|
22803
|
-
setState((prev) => ({
|
|
22804
|
-
...prev,
|
|
22805
|
-
selectedLogIndex: Math.min(prev.selectedLogIndex + 1, maxLogIndex)
|
|
22806
|
-
}));
|
|
22501
|
+
}
|
|
22502
|
+
if (input === "d") {
|
|
22503
|
+
if (doneView) {
|
|
22504
|
+
showStatus(STATUS.readOnly, "info");
|
|
22807
22505
|
return;
|
|
22808
22506
|
}
|
|
22809
|
-
if (
|
|
22810
|
-
|
|
22811
|
-
...prev,
|
|
22812
|
-
selectedLogIndex: Math.max(prev.selectedLogIndex - 1, 0)
|
|
22813
|
-
}));
|
|
22507
|
+
if (!currentTask) {
|
|
22508
|
+
showStatus(STATUS.noDocument, "error");
|
|
22814
22509
|
return;
|
|
22815
22510
|
}
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
else expanded.add(task.id);
|
|
22823
|
-
return { ...prev, expandedLogIds: expanded };
|
|
22824
|
-
});
|
|
22825
|
-
}
|
|
22511
|
+
setMode("delete-confirm");
|
|
22512
|
+
return;
|
|
22513
|
+
}
|
|
22514
|
+
if (input === "e") {
|
|
22515
|
+
if (!currentTask) {
|
|
22516
|
+
showStatus(STATUS.noDocument, "error");
|
|
22826
22517
|
return;
|
|
22827
22518
|
}
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22519
|
+
openInEditor(currentTask.id);
|
|
22520
|
+
return;
|
|
22521
|
+
}
|
|
22522
|
+
if (input === "p") {
|
|
22523
|
+
if (!currentTask) return;
|
|
22524
|
+
if (doneView) {
|
|
22525
|
+
showStatus(STATUS.readOnly, "info");
|
|
22834
22526
|
return;
|
|
22835
22527
|
}
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
|
|
22841
|
-
|
|
22842
|
-
return;
|
|
22528
|
+
const result = cyclePriorityAction(filePath, currentTask.id);
|
|
22529
|
+
if (result.success) {
|
|
22530
|
+
showStatus(result.message || STATUS.priorityUpdated("updated"), "success");
|
|
22531
|
+
loadBrainfile(true);
|
|
22532
|
+
} else {
|
|
22533
|
+
showStatus(result.error || STATUS.priorityFailed, "error");
|
|
22843
22534
|
}
|
|
22844
|
-
|
|
22845
|
-
|
|
22535
|
+
return;
|
|
22536
|
+
}
|
|
22537
|
+
if (input === "y") {
|
|
22538
|
+
if (!currentTask) return;
|
|
22539
|
+
const result = copyToClipboard(currentTask.id);
|
|
22540
|
+
showStatus(
|
|
22541
|
+
result.success ? STATUS.copied(currentTask.id) : result.error || STATUS.copyFailed,
|
|
22542
|
+
result.success ? "success" : "error"
|
|
22543
|
+
);
|
|
22544
|
+
return;
|
|
22545
|
+
}
|
|
22546
|
+
if (input === "N") {
|
|
22547
|
+
if (doneView) {
|
|
22548
|
+
showStatus(STATUS.readOnly, "info");
|
|
22846
22549
|
return;
|
|
22847
22550
|
}
|
|
22848
|
-
|
|
22849
|
-
|
|
22551
|
+
setMode("add", { newTaskTitle: "", addThenEdit: true });
|
|
22552
|
+
return;
|
|
22553
|
+
}
|
|
22554
|
+
if (input === "A") {
|
|
22555
|
+
if (!currentTask) return;
|
|
22556
|
+
if (doneView) {
|
|
22557
|
+
showStatus(STATUS.readOnly, "info");
|
|
22850
22558
|
return;
|
|
22851
22559
|
}
|
|
22852
|
-
|
|
22853
|
-
|
|
22560
|
+
showStatus(STATUS.archiving, "info");
|
|
22561
|
+
archiveTaskActionAsync(filePath, currentTask.id).then((result) => {
|
|
22562
|
+
showStatus(
|
|
22563
|
+
result.success ? result.message || STATUS.archived(currentTask.id) : result.error || STATUS.archiveFailed,
|
|
22564
|
+
result.success ? "success" : "error"
|
|
22565
|
+
);
|
|
22566
|
+
if (result.success) loadBrainfile(true);
|
|
22567
|
+
}).catch((err) => showStatus(`${STATUS.archiveFailed}: ${err}`, "error"));
|
|
22568
|
+
return;
|
|
22569
|
+
}
|
|
22570
|
+
if (key.downArrow || input === "j") {
|
|
22571
|
+
setState((prev) => ({
|
|
22572
|
+
...prev,
|
|
22573
|
+
selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxRowIndex)
|
|
22574
|
+
}));
|
|
22575
|
+
return;
|
|
22576
|
+
}
|
|
22577
|
+
if (key.upArrow || input === "k") {
|
|
22578
|
+
setState((prev) => ({
|
|
22579
|
+
...prev,
|
|
22580
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0)
|
|
22581
|
+
}));
|
|
22582
|
+
return;
|
|
22583
|
+
}
|
|
22584
|
+
if (key.ctrl && input === "d") {
|
|
22585
|
+
setState((prev) => ({
|
|
22586
|
+
...prev,
|
|
22587
|
+
selectedTaskIndex: Math.min(
|
|
22588
|
+
prev.selectedTaskIndex + Math.floor(viewportHeight / 2),
|
|
22589
|
+
maxRowIndex
|
|
22590
|
+
)
|
|
22591
|
+
}));
|
|
22592
|
+
return;
|
|
22593
|
+
}
|
|
22594
|
+
if (key.ctrl && input === "u") {
|
|
22595
|
+
setState((prev) => ({
|
|
22596
|
+
...prev,
|
|
22597
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0)
|
|
22598
|
+
}));
|
|
22599
|
+
return;
|
|
22600
|
+
}
|
|
22601
|
+
if (input === "g") {
|
|
22602
|
+
setState((prev) => ({ ...prev, selectedTaskIndex: 0 }));
|
|
22603
|
+
return;
|
|
22604
|
+
}
|
|
22605
|
+
if (input === "G") {
|
|
22606
|
+
setState((prev) => ({ ...prev, selectedTaskIndex: maxRowIndex }));
|
|
22607
|
+
return;
|
|
22608
|
+
}
|
|
22609
|
+
if (key.tab || key.rightArrow || input === "l") {
|
|
22610
|
+
if (!doneView && filteredColumnsLength > 0) {
|
|
22611
|
+
setState((prev) => {
|
|
22612
|
+
const nextIndex = (prev.activeColumnIndex + 1) % filteredColumnsLength;
|
|
22613
|
+
rememberColumn(nextIndex);
|
|
22614
|
+
return { ...prev, activeColumnIndex: nextIndex, selectedTaskIndex: 0 };
|
|
22615
|
+
});
|
|
22616
|
+
}
|
|
22617
|
+
return;
|
|
22618
|
+
}
|
|
22619
|
+
if (key.shift && key.tab || key.leftArrow || input === "h") {
|
|
22620
|
+
if (!doneView && filteredColumnsLength > 0) {
|
|
22621
|
+
setState((prev) => {
|
|
22622
|
+
const nextIndex = prev.activeColumnIndex === 0 ? filteredColumnsLength - 1 : prev.activeColumnIndex - 1;
|
|
22623
|
+
rememberColumn(nextIndex);
|
|
22624
|
+
return { ...prev, activeColumnIndex: nextIndex, selectedTaskIndex: 0 };
|
|
22625
|
+
});
|
|
22854
22626
|
}
|
|
22627
|
+
return;
|
|
22855
22628
|
}
|
|
22629
|
+
if (key.escape) {
|
|
22630
|
+
setState((prev) => ({ ...prev, filterQuery: "", selectedTaskIndex: 0 }));
|
|
22631
|
+
}
|
|
22632
|
+
return;
|
|
22856
22633
|
});
|
|
22857
22634
|
}
|
|
22858
22635
|
|
|
22636
|
+
// src/tui/typeCycle.ts
|
|
22637
|
+
var FIXED_TYPE_ORDER = ["task", "epic", "spec", "plan", "adr"];
|
|
22638
|
+
function getTypeCycleOptions(board) {
|
|
22639
|
+
const configured = Object.keys(
|
|
22640
|
+
board?.types ?? {}
|
|
22641
|
+
);
|
|
22642
|
+
const configuredSet = new Set(configured);
|
|
22643
|
+
const known = FIXED_TYPE_ORDER.filter((type) => type === "task" || configuredSet.has(type));
|
|
22644
|
+
const extra = configured.filter((type) => !FIXED_TYPE_ORDER.includes(type)).sort();
|
|
22645
|
+
return ["all", ...known, ...extra];
|
|
22646
|
+
}
|
|
22647
|
+
|
|
22859
22648
|
// src/tui/components/HeaderBar.tsx
|
|
22860
|
-
var
|
|
22649
|
+
var import_react37 = __toESM(require_react());
|
|
22861
22650
|
function HeaderBar({
|
|
22862
22651
|
title,
|
|
22863
22652
|
columns,
|
|
@@ -22867,30 +22656,38 @@ function HeaderBar({
|
|
|
22867
22656
|
filterActive,
|
|
22868
22657
|
matchCount,
|
|
22869
22658
|
totalCount,
|
|
22870
|
-
panelLabel
|
|
22659
|
+
panelLabel,
|
|
22660
|
+
activeType
|
|
22871
22661
|
}) {
|
|
22872
|
-
const affordances =
|
|
22662
|
+
const affordances = CHROME.filterAffordance;
|
|
22873
22663
|
const boardTitle = truncate(title, 24);
|
|
22874
22664
|
const separator = ` ${GLYPHS.pointer} `;
|
|
22875
|
-
const
|
|
22665
|
+
const typeLabel = activeType && activeType !== "all" ? ` \xB7 ${activeType}` : "";
|
|
22666
|
+
const tabsWidth = (panelLabel ? panelLabel.length : columns.reduce((sum, column, index) => {
|
|
22876
22667
|
const active = index === activeColumnIndex;
|
|
22877
22668
|
return sum + (index > 0 ? 3 : 0) + `${column.title} ${column.tasks?.length ?? 0}`.length + (active ? 1 : 0);
|
|
22878
|
-
}, 0);
|
|
22669
|
+
}, 0)) + typeLabel.length;
|
|
22879
22670
|
const used = boardTitle.length + separator.length + tabsWidth;
|
|
22880
22671
|
const gap = Math.max(1, width - 1 - used - affordances.length - 1);
|
|
22881
|
-
return
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
22893
|
-
|
|
22672
|
+
return (
|
|
22673
|
+
// flexShrink={0}: a detail v2 pane can legitimately render taller than the
|
|
22674
|
+
// nominal viewport (§B1's many optional sections). Without this, yoga
|
|
22675
|
+
// shrinks *this* sibling instead of letting the column overflow, and the
|
|
22676
|
+
// header silently loses rows rather than the frame simply growing.
|
|
22677
|
+
/* @__PURE__ */ import_react37.default.createElement(Box_default, { flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ import_react37.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react37.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.text, bold: true }, boardTitle), /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textDim }, separator), panelLabel ? /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.text }, panelLabel) : columns.map((column, index) => {
|
|
22678
|
+
const active = index === activeColumnIndex;
|
|
22679
|
+
const count = column.tasks?.length ?? 0;
|
|
22680
|
+
return /* @__PURE__ */ import_react37.default.createElement(Text, { key: column.id }, index > 0 ? /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textDim }, " \u2502 ") : null, /* @__PURE__ */ import_react37.default.createElement(Text, { color: active ? PALETTE.text : PALETTE.textMuted, bold: active }, `${column.title} ${count}`), active ? /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.text, bold: true }, "*") : null);
|
|
22681
|
+
}), typeLabel ? /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textDim }, typeLabel) : null, /* @__PURE__ */ import_react37.default.createElement(Text, null, pad(gap)), /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textMuted }, affordances))), filterActive ? /* @__PURE__ */ import_react37.default.createElement(
|
|
22682
|
+
FilterLine,
|
|
22683
|
+
{
|
|
22684
|
+
query: filterQuery,
|
|
22685
|
+
width,
|
|
22686
|
+
matchCount,
|
|
22687
|
+
totalCount
|
|
22688
|
+
}
|
|
22689
|
+
) : /* @__PURE__ */ import_react37.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textDim }, RULE.repeat(Math.max(1, width - 2)))))
|
|
22690
|
+
);
|
|
22894
22691
|
}
|
|
22895
22692
|
function FilterLine({
|
|
22896
22693
|
query,
|
|
@@ -22898,25 +22695,32 @@ function FilterLine({
|
|
|
22898
22695
|
matchCount,
|
|
22899
22696
|
totalCount
|
|
22900
22697
|
}) {
|
|
22901
|
-
const count = matchCount === void 0 || totalCount === void 0 ? "" :
|
|
22698
|
+
const count = matchCount === void 0 || totalCount === void 0 ? "" : matchCountLabel(matchCount, totalCount, query);
|
|
22902
22699
|
const input = `/${query}`;
|
|
22903
22700
|
const gap = Math.max(1, width - 2 - input.length - 1 - count.length);
|
|
22904
|
-
return /* @__PURE__ */
|
|
22701
|
+
return /* @__PURE__ */ import_react37.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react37.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.accent }, input), /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.accent }, GLYPHS.cursor), /* @__PURE__ */ import_react37.default.createElement(Text, null, pad(gap)), /* @__PURE__ */ import_react37.default.createElement(Text, { color: PALETTE.textMuted }, count || " ")));
|
|
22905
22702
|
}
|
|
22906
22703
|
|
|
22907
22704
|
// src/tui/components/FooterBar.tsx
|
|
22908
|
-
var
|
|
22909
|
-
function browseActions(selected) {
|
|
22705
|
+
var import_react38 = __toESM(require_react());
|
|
22706
|
+
function browseActions(selected, archived = false) {
|
|
22707
|
+
if (archived) return ["\u21B5 detail", "e edit", "t type", "L board", "q quit"];
|
|
22910
22708
|
const actions = ["\u21B5 detail", "m move"];
|
|
22911
22709
|
if (isCompletable(selected)) actions.push("c complete");
|
|
22912
|
-
actions.push("a add", "tab column", "q quit");
|
|
22710
|
+
actions.push("a add", "t type", "L done", "tab column", "q quit");
|
|
22913
22711
|
return actions;
|
|
22914
22712
|
}
|
|
22915
|
-
function detailActions(
|
|
22713
|
+
function detailActions(ctx) {
|
|
22916
22714
|
const actions = [];
|
|
22917
|
-
if (
|
|
22918
|
-
actions.push("
|
|
22919
|
-
if (
|
|
22715
|
+
if (ctx.hasChildren) actions.push("\u21B5 open child");
|
|
22716
|
+
if (ctx.hasSubtasks) actions.push("space toggle");
|
|
22717
|
+
if (ctx.bodyOverflows) actions.push("u/d scroll");
|
|
22718
|
+
if (ctx.hasParent) actions.push("p parent");
|
|
22719
|
+
actions.push("e edit");
|
|
22720
|
+
if (!ctx.archived) {
|
|
22721
|
+
actions.push("m move");
|
|
22722
|
+
if (isCompletable(ctx.task)) actions.push("c complete");
|
|
22723
|
+
}
|
|
22920
22724
|
actions.push("esc back");
|
|
22921
22725
|
return actions;
|
|
22922
22726
|
}
|
|
@@ -22927,20 +22731,30 @@ function FooterBar({
|
|
|
22927
22731
|
stateChip,
|
|
22928
22732
|
showRule = true
|
|
22929
22733
|
}) {
|
|
22930
|
-
const
|
|
22734
|
+
const quit = actions.includes("q quit") ? "q quit" : "";
|
|
22735
|
+
const shown = quit ? actions.filter((action) => action !== "q quit") : actions;
|
|
22736
|
+
const left = itemCount === void 0 ? shown.join(" \xB7 ") : [itemCountLabel(itemCount), ...shown].join(" \xB7 ");
|
|
22931
22737
|
const chip = stateChip ?? "";
|
|
22932
|
-
const
|
|
22738
|
+
const right = [quit, chip].filter(Boolean).join(" ");
|
|
22739
|
+
const available = Math.max(1, width - 2 - right.length - 2);
|
|
22933
22740
|
const text = truncate(left, available);
|
|
22934
|
-
const gap = Math.max(1, width - 2 - text.length -
|
|
22935
|
-
return
|
|
22741
|
+
const gap = Math.max(1, width - 2 - text.length - right.length);
|
|
22742
|
+
return (
|
|
22743
|
+
// flexShrink={0}: see HeaderBar's matching comment — the footer must not
|
|
22744
|
+
// be the thing that silently loses rows when detail v2 overflows.
|
|
22745
|
+
/* @__PURE__ */ import_react38.default.createElement(Box_default, { flexDirection: "column", flexShrink: 0 }, showRule ? /* @__PURE__ */ import_react38.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react38.default.createElement(Text, { color: PALETTE.textDim }, RULE.repeat(Math.max(1, width - 2)))) : null, /* @__PURE__ */ import_react38.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react38.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react38.default.createElement(Text, { color: PALETTE.textMuted }, text), /* @__PURE__ */ import_react38.default.createElement(Text, null, pad(gap)), quit ? /* @__PURE__ */ import_react38.default.createElement(Text, { color: PALETTE.textMuted }, quit) : null, quit && chip ? /* @__PURE__ */ import_react38.default.createElement(Text, null, pad(2)) : null, /* @__PURE__ */ import_react38.default.createElement(Text, { color: PALETTE.textSecondary }, chip || " "))))
|
|
22746
|
+
);
|
|
22936
22747
|
}
|
|
22937
22748
|
|
|
22938
22749
|
// src/tui/components/DocumentRow.tsx
|
|
22939
|
-
var
|
|
22750
|
+
var import_react39 = __toESM(require_react());
|
|
22940
22751
|
var MAX_CHIPS = 3;
|
|
22941
22752
|
function buildChips(row) {
|
|
22942
22753
|
const chips = [];
|
|
22943
22754
|
const { task } = row;
|
|
22755
|
+
if (row.collapsed && row.childCount) {
|
|
22756
|
+
chips.push({ text: `${row.childCount} hidden`, color: PALETTE.textMuted });
|
|
22757
|
+
}
|
|
22944
22758
|
const contractState = getContractState(task);
|
|
22945
22759
|
if (contractState) {
|
|
22946
22760
|
chips.push({ text: contractState, color: getContractStateColor(contractState) });
|
|
@@ -22960,10 +22774,11 @@ function buildPriorityMarker(row) {
|
|
|
22960
22774
|
if (priority !== "high" && priority !== "critical") return void 0;
|
|
22961
22775
|
return { text: `${GLYPHS.warning} ${priority}`, color: getPriorityColor(priority) };
|
|
22962
22776
|
}
|
|
22963
|
-
function DocumentRow({ row, selected, width, idWidth }) {
|
|
22777
|
+
function DocumentRow({ row, selected, width, idWidth, archived = false }) {
|
|
22964
22778
|
const { task, depth, orphanParentId } = row;
|
|
22965
|
-
const
|
|
22966
|
-
const
|
|
22779
|
+
const hasChildren = Boolean(row.childCount);
|
|
22780
|
+
const glyph = hasChildren ? row.collapsed ? GLYPHS.parentCollapsed : GLYPHS.parentExpanded : getTypeGlyph(task.type);
|
|
22781
|
+
const indent = `${selected ? GLYPHS.cursor : " "}${pad(depth * 2)}`;
|
|
22967
22782
|
const glyphCell = `${glyph || " "} `;
|
|
22968
22783
|
const idCell = `${task.id.padEnd(idWidth)} `;
|
|
22969
22784
|
const leftWidth = indent.length + glyphCell.length + idCell.length;
|
|
@@ -22979,205 +22794,107 @@ function DocumentRow({ row, selected, width, idWidth }) {
|
|
|
22979
22794
|
const title = truncate(task.title, titleWidth);
|
|
22980
22795
|
const gap = Math.max(1, width - leftWidth - title.length - rightWidth - 1);
|
|
22981
22796
|
const dim = selected ? void 0 : PALETTE.textMuted;
|
|
22982
|
-
return /* @__PURE__ */
|
|
22797
|
+
return /* @__PURE__ */ import_react39.default.createElement(Text, { wrap: "truncate", inverse: selected }, /* @__PURE__ */ import_react39.default.createElement(Text, null, indent), /* @__PURE__ */ import_react39.default.createElement(Text, { color: selected ? void 0 : PALETTE.textSecondary }, glyphCell), /* @__PURE__ */ import_react39.default.createElement(Text, { color: dim }, idCell), /* @__PURE__ */ import_react39.default.createElement(Text, { color: selected ? void 0 : archived ? PALETTE.textMuted : PALETTE.text }, title), /* @__PURE__ */ import_react39.default.createElement(Text, null, pad(gap)), rightSegments.map((seg, index) => /* @__PURE__ */ import_react39.default.createElement(Text, { key: `${seg.text}-${index}` }, index > 0 ? /* @__PURE__ */ import_react39.default.createElement(Text, null, pad(2)) : null, /* @__PURE__ */ import_react39.default.createElement(Text, { color: selected ? void 0 : seg.color }, seg.text))), /* @__PURE__ */ import_react39.default.createElement(Text, null, pad(1)));
|
|
22983
22798
|
}
|
|
22984
22799
|
|
|
22985
22800
|
// src/tui/components/DocumentList.tsx
|
|
22986
|
-
var
|
|
22801
|
+
var import_react40 = __toESM(require_react());
|
|
22987
22802
|
function DocumentList({
|
|
22988
22803
|
rows,
|
|
22989
22804
|
selectedIndex,
|
|
22990
22805
|
viewportHeight,
|
|
22991
22806
|
width,
|
|
22992
|
-
|
|
22807
|
+
archived = false,
|
|
22808
|
+
emptyMessage = EMPTY.noDocuments
|
|
22993
22809
|
}) {
|
|
22994
22810
|
if (rows.length === 0) {
|
|
22995
|
-
return /* @__PURE__ */
|
|
22811
|
+
return /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, emptyMessage));
|
|
22996
22812
|
}
|
|
22997
22813
|
const { visible, start } = windowRows(rows, selectedIndex, viewportHeight);
|
|
22998
22814
|
const idWidth = Math.max(6, ...visible.map((row) => row.task.id.length));
|
|
22999
|
-
return /* @__PURE__ */
|
|
22815
|
+
return /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1 }, visible.map((row, index) => /* @__PURE__ */ import_react40.default.createElement(
|
|
23000
22816
|
DocumentRow,
|
|
23001
22817
|
{
|
|
23002
22818
|
key: row.task.id,
|
|
23003
22819
|
row,
|
|
23004
22820
|
selected: start + index === selectedIndex,
|
|
23005
22821
|
width,
|
|
23006
|
-
idWidth
|
|
22822
|
+
idWidth,
|
|
22823
|
+
archived
|
|
23007
22824
|
}
|
|
23008
22825
|
)));
|
|
23009
22826
|
}
|
|
23010
22827
|
|
|
23011
|
-
// src/tui/components/DetailView.tsx
|
|
23012
|
-
var import_react40 = __toESM(require_react());
|
|
23013
|
-
function DetailView({
|
|
23014
|
-
task,
|
|
23015
|
-
columnLabel,
|
|
23016
|
-
width,
|
|
23017
|
-
height,
|
|
23018
|
-
selectedSubtaskIndex,
|
|
23019
|
-
parent
|
|
23020
|
-
}) {
|
|
23021
|
-
const contentWidth = Math.max(20, width - 2);
|
|
23022
|
-
const stacked = contentWidth < 60;
|
|
23023
|
-
const priority = task.priority;
|
|
23024
|
-
const contractState = getContractState(task);
|
|
23025
|
-
const subtasks = task.subtasks ?? [];
|
|
23026
|
-
const files = task.relatedFiles ?? [];
|
|
23027
|
-
const state = priority ? `${columnLabel} \xB7 ${priority}` : columnLabel;
|
|
23028
|
-
const headWidth = Math.max(1, contentWidth - state.length - 2);
|
|
23029
|
-
const head = truncate(`${task.id} ${task.title}`, headWidth);
|
|
23030
|
-
const bodyLines = buildBodyLines(task.description, contentWidth);
|
|
23031
|
-
const bodyBudget = Math.max(
|
|
23032
|
-
1,
|
|
23033
|
-
height - 8 - (stacked ? 3 : 1) - subtasks.length - (files.length ? 2 : 0)
|
|
23034
|
-
);
|
|
23035
|
-
const body = bodyLines.slice(0, bodyBudget);
|
|
23036
|
-
const bodyTruncated = bodyLines.length > body.length;
|
|
23037
|
-
return /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", width }, /* @__PURE__ */ import_react40.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react40.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.text, bold: true }, head), /* @__PURE__ */ import_react40.default.createElement(Text, null, pad(Math.max(1, contentWidth - head.length - state.length))), /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textSecondary }, columnLabel), priority ? /* @__PURE__ */ import_react40.default.createElement(Text, null, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textDim }, " \xB7 "), /* @__PURE__ */ import_react40.default.createElement(Text, { color: getPriorityColor(priority) }, priority)) : null)), /* @__PURE__ */ import_react40.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textDim }, RULE.repeat(contentWidth))), /* @__PURE__ */ import_react40.default.createElement(MetadataBlock, { task, parent, stacked, width }), /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, body.length === 0 ? /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, "(no description)") : body.map((line, index) => /* @__PURE__ */ import_react40.default.createElement(
|
|
23038
|
-
Text,
|
|
23039
|
-
{
|
|
23040
|
-
key: `body-${index}`,
|
|
23041
|
-
wrap: "truncate",
|
|
23042
|
-
bold: line.kind === "heading",
|
|
23043
|
-
color: line.kind === "code" ? PALETTE.textMuted : line.kind === "heading" ? PALETTE.text : PALETTE.textSecondary
|
|
23044
|
-
},
|
|
23045
|
-
line.text || " "
|
|
23046
|
-
)), bodyTruncated ? /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textDim }, "\u2026") : null), subtasks.length > 0 || contractState ? /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, marginTop: 1 }, /* @__PURE__ */ import_react40.default.createElement(Text, null, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, "subtasks"), /* @__PURE__ */ import_react40.default.createElement(Text, null, pad(Math.max(1, contentWidth - 8 - 8))), /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, "contract")), renderSubtaskAndContractRows(
|
|
23047
|
-
subtasks,
|
|
23048
|
-
selectedSubtaskIndex,
|
|
23049
|
-
contractState,
|
|
23050
|
-
contentWidth
|
|
23051
|
-
)) : null, files.length > 0 ? /* @__PURE__ */ import_react40.default.createElement(Box_default, { paddingLeft: 1, marginTop: 1, width }, /* @__PURE__ */ import_react40.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, "files "), /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textSecondary }, truncate(files.join(" \xB7 "), Math.max(1, contentWidth - 7))))) : null);
|
|
23052
|
-
}
|
|
23053
|
-
function renderSubtaskAndContractRows(subtasks, selectedSubtaskIndex, contractState, contentWidth) {
|
|
23054
|
-
const rowCount = Math.max(subtasks.length, 1);
|
|
23055
|
-
const rows = [];
|
|
23056
|
-
for (let index = 0; index < rowCount; index += 1) {
|
|
23057
|
-
const subtask = subtasks[index];
|
|
23058
|
-
const selected = subtask !== void 0 && index === selectedSubtaskIndex;
|
|
23059
|
-
const glyph = subtask?.completed ? GLYPHS.subtaskDone : GLYPHS.subtaskOpen;
|
|
23060
|
-
const label = subtask ? ` ${glyph} ${subtask.title}` : " (none)";
|
|
23061
|
-
const right = index === 0 ? contractState ?? (subtasks.length > 0 ? "(none)" : void 0) : void 0;
|
|
23062
|
-
const rightText = right ?? "";
|
|
23063
|
-
const left = truncate(label, Math.max(1, contentWidth - rightText.length - 2));
|
|
23064
|
-
rows.push(
|
|
23065
|
-
/* @__PURE__ */ import_react40.default.createElement(Text, { key: `sub-${index}`, wrap: "truncate" }, /* @__PURE__ */ import_react40.default.createElement(
|
|
23066
|
-
Text,
|
|
23067
|
-
{
|
|
23068
|
-
color: selected ? void 0 : subtask?.completed ? PALETTE.textMuted : PALETTE.textSecondary,
|
|
23069
|
-
inverse: selected
|
|
23070
|
-
},
|
|
23071
|
-
left
|
|
23072
|
-
), /* @__PURE__ */ import_react40.default.createElement(Text, null, pad(Math.max(1, contentWidth - left.length - rightText.length))), rightText ? /* @__PURE__ */ import_react40.default.createElement(Text, { color: getContractStateColor(right) }, rightText) : null)
|
|
23073
|
-
);
|
|
23074
|
-
}
|
|
23075
|
-
return rows;
|
|
23076
|
-
}
|
|
23077
|
-
function MetadataBlock({
|
|
23078
|
-
task,
|
|
23079
|
-
parent,
|
|
23080
|
-
stacked,
|
|
23081
|
-
width
|
|
23082
|
-
}) {
|
|
23083
|
-
const tags = task.tags?.length ? task.tags.map((t) => `#${t}`).join(" ") : "\u2014";
|
|
23084
|
-
const assignee = task.assignee || "\u2014";
|
|
23085
|
-
const created = task.createdAt ? task.createdAt.slice(0, 10) : "\u2014";
|
|
23086
|
-
const parentLabel = task.parentId ? parent ? task.parentId : `${task.parentId} \u2197` : "\u2014";
|
|
23087
|
-
const fields = [
|
|
23088
|
-
["tags", tags],
|
|
23089
|
-
["assignee", assignee],
|
|
23090
|
-
["created", created],
|
|
23091
|
-
["parent", parentLabel]
|
|
23092
|
-
];
|
|
23093
|
-
if (stacked) {
|
|
23094
|
-
return /* @__PURE__ */ import_react40.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1, width }, fields.map(([label, value]) => /* @__PURE__ */ import_react40.default.createElement(Text, { key: label, wrap: "truncate" }, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, `${label} `), /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textSecondary }, value))));
|
|
23095
|
-
}
|
|
23096
|
-
return /* @__PURE__ */ import_react40.default.createElement(Box_default, { paddingLeft: 1, width }, /* @__PURE__ */ import_react40.default.createElement(Text, { wrap: "truncate" }, fields.map(([label, value], index) => /* @__PURE__ */ import_react40.default.createElement(Text, { key: label }, index > 0 ? /* @__PURE__ */ import_react40.default.createElement(Text, null, pad(4)) : null, /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textMuted }, `${label} `), /* @__PURE__ */ import_react40.default.createElement(Text, { color: PALETTE.textSecondary }, value)))));
|
|
23097
|
-
}
|
|
23098
|
-
function buildBodyLines(description, width) {
|
|
23099
|
-
if (!description) return [];
|
|
23100
|
-
const out = [];
|
|
23101
|
-
let inFence = false;
|
|
23102
|
-
for (const raw of description.split("\n")) {
|
|
23103
|
-
const line = raw.replace(/\s+$/, "");
|
|
23104
|
-
if (line.trim().startsWith("```")) {
|
|
23105
|
-
inFence = !inFence;
|
|
23106
|
-
continue;
|
|
23107
|
-
}
|
|
23108
|
-
if (inFence) {
|
|
23109
|
-
out.push({ text: ` ${truncate(line, Math.max(1, width - 2))}`, kind: "code" });
|
|
23110
|
-
continue;
|
|
23111
|
-
}
|
|
23112
|
-
if (/^#{1,6}\s/.test(line)) {
|
|
23113
|
-
out.push({ text: truncate(line.replace(/^#{1,6}\s*/, ""), width), kind: "heading" });
|
|
23114
|
-
continue;
|
|
23115
|
-
}
|
|
23116
|
-
if (/^\s*[-*+]\s/.test(line)) {
|
|
23117
|
-
const item = line.replace(/^\s*[-*+]\s*/, "");
|
|
23118
|
-
for (const wrapped of wrapText2(item, Math.max(1, width - 4))) {
|
|
23119
|
-
out.push({ text: ` \u2022 ${wrapped}`, kind: "list" });
|
|
23120
|
-
}
|
|
23121
|
-
continue;
|
|
23122
|
-
}
|
|
23123
|
-
if (line.trim() === "") {
|
|
23124
|
-
out.push({ text: "", kind: "text" });
|
|
23125
|
-
continue;
|
|
23126
|
-
}
|
|
23127
|
-
for (const wrapped of wrapText2(line, width)) {
|
|
23128
|
-
out.push({ text: wrapped, kind: "text" });
|
|
23129
|
-
}
|
|
23130
|
-
}
|
|
23131
|
-
return out;
|
|
23132
|
-
}
|
|
23133
|
-
|
|
23134
22828
|
// src/tui/components/HelpOverlay.tsx
|
|
23135
22829
|
var import_react41 = __toESM(require_react());
|
|
23136
|
-
var
|
|
23137
|
-
|
|
23138
|
-
|
|
23139
|
-
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23145
|
-
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
|
|
23169
|
-
|
|
23170
|
-
|
|
23171
|
-
|
|
23172
|
-
|
|
23173
|
-
|
|
23174
|
-
|
|
22830
|
+
var LIST = {
|
|
22831
|
+
heading: "list",
|
|
22832
|
+
rows: [
|
|
22833
|
+
["j k \u2191\u2193", "move"],
|
|
22834
|
+
["h l tab \u2190\u2192", "column"],
|
|
22835
|
+
["g G", "top / bottom"],
|
|
22836
|
+
["ctrl-d/u", "half page"],
|
|
22837
|
+
["\u21B5", "open"],
|
|
22838
|
+
["t", "type"],
|
|
22839
|
+
["L", "done"],
|
|
22840
|
+
["/", "filter"]
|
|
22841
|
+
]
|
|
22842
|
+
};
|
|
22843
|
+
var DETAIL = {
|
|
22844
|
+
heading: "detail",
|
|
22845
|
+
rows: [
|
|
22846
|
+
["\u21B5", "open child"],
|
|
22847
|
+
["space", "toggle subtask"],
|
|
22848
|
+
["u d", "scroll body"],
|
|
22849
|
+
["p", "parent"],
|
|
22850
|
+
["esc", "back"]
|
|
22851
|
+
]
|
|
22852
|
+
};
|
|
22853
|
+
var ACTIONS = {
|
|
22854
|
+
heading: "actions",
|
|
22855
|
+
rows: [
|
|
22856
|
+
["a n", "add"],
|
|
22857
|
+
["N", "add + edit"],
|
|
22858
|
+
["m", "move"],
|
|
22859
|
+
["c", "complete"],
|
|
22860
|
+
["e", "edit"],
|
|
22861
|
+
["p", "priority"],
|
|
22862
|
+
["space", "collapse"],
|
|
22863
|
+
["d", "delete"],
|
|
22864
|
+
["y", "copy id"],
|
|
22865
|
+
["r", "reload"],
|
|
22866
|
+
["A", "archive"]
|
|
22867
|
+
]
|
|
22868
|
+
};
|
|
22869
|
+
var FILTER = {
|
|
22870
|
+
heading: "filter",
|
|
22871
|
+
rows: [
|
|
22872
|
+
["p:high", "priority"],
|
|
22873
|
+
["#tag t:tag", "tag"],
|
|
22874
|
+
["@name", "assignee"],
|
|
22875
|
+
["type:epic", "type"],
|
|
22876
|
+
["contract:ready", "contract"],
|
|
22877
|
+
["due:overdue", "due"]
|
|
22878
|
+
]
|
|
22879
|
+
};
|
|
22880
|
+
var COLUMNS = [
|
|
22881
|
+
[LIST, DETAIL],
|
|
22882
|
+
[ACTIONS, FILTER]
|
|
23175
22883
|
];
|
|
23176
|
-
var KEYS_WIDTH =
|
|
22884
|
+
var KEYS_WIDTH = 14;
|
|
23177
22885
|
function HelpOverlay({ termWidth, termHeight }) {
|
|
23178
|
-
const columnWidth = Math.max(24, Math.floor((termWidth - 4) /
|
|
22886
|
+
const columnWidth = Math.max(24, Math.floor((termWidth - 4) / COLUMNS.length));
|
|
23179
22887
|
const descriptionWidth = Math.max(6, columnWidth - KEYS_WIDTH - 2);
|
|
23180
|
-
return /* @__PURE__ */ import_react41.default.createElement(Box_default, { flexDirection: "column", width: termWidth, height: termHeight, paddingLeft: 1 }, /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.text, bold: true },
|
|
22888
|
+
return /* @__PURE__ */ import_react41.default.createElement(Box_default, { flexDirection: "column", width: termWidth, height: termHeight, paddingLeft: 1 }, /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.text, bold: true }, CHROME.helpTitle), /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.textDim }, RULE.repeat(Math.max(1, termWidth - 2))), /* @__PURE__ */ import_react41.default.createElement(Box_default, { marginTop: 1 }, COLUMNS.map((column, columnIndex) => /* @__PURE__ */ import_react41.default.createElement(Box_default, { key: columnIndex, flexDirection: "column", width: columnWidth }, column.map((group, groupIndex) => /* @__PURE__ */ import_react41.default.createElement(
|
|
22889
|
+
Box_default,
|
|
22890
|
+
{
|
|
22891
|
+
key: group.heading,
|
|
22892
|
+
flexDirection: "column",
|
|
22893
|
+
marginTop: groupIndex === 0 ? 0 : 1
|
|
22894
|
+
},
|
|
22895
|
+
/* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.textSecondary, bold: true }, group.heading),
|
|
22896
|
+
group.rows.map(([keys, description]) => /* @__PURE__ */ import_react41.default.createElement(Text, { key: keys, wrap: "truncate" }, /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.text }, keys.padEnd(KEYS_WIDTH)), /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.textMuted }, truncate(description, descriptionWidth))))
|
|
22897
|
+
))))), /* @__PURE__ */ import_react41.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react41.default.createElement(Text, { color: PALETTE.textDim }, CHROME.helpDismiss)));
|
|
23181
22898
|
}
|
|
23182
22899
|
|
|
23183
22900
|
// src/tui/components/Overlays.tsx
|
|
@@ -23198,190 +22915,46 @@ function MoveOverlay({
|
|
|
23198
22915
|
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "move"), /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, ` ${taskId} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.text }, truncate(taskTitle, Math.max(1, width - taskId.length - 6)))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", marginTop: 1, paddingLeft: 2 }, columns.map((column, index) => /* @__PURE__ */ import_react42.default.createElement(Text, { key: column.id, inverse: index === selectedIndex, wrap: "truncate" }, `${index + 1} ${column.title}`))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "\u2191\u2193 select \xB7 1-", columns.length, " jump \xB7 \u21B5 confirm \xB7 esc cancel")));
|
|
23199
22916
|
}
|
|
23200
22917
|
function DeleteConfirmOverlay({ taskId, taskTitle, width }) {
|
|
23201
|
-
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.error }, "delete"), /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, ` ${taskId} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.text }, truncate(taskTitle, Math.max(1, width - taskId.length - 6)))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "This cannot be undone.")), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "y delete \xB7 n cancel")));
|
|
22918
|
+
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.error }, "delete"), /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, ` ${taskId} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.text }, truncate(taskTitle, Math.max(1, width - taskId.length - 6)))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "Deletes the file. This cannot be undone.")), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "y delete \xB7 n cancel")));
|
|
23202
22919
|
}
|
|
23203
22920
|
function CompleteConfirmOverlay({ target, width }) {
|
|
23204
|
-
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.warning }, "complete"), /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, ` ${target.id} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.text }, truncate(target.title, Math.max(1, width - target.id.length - 6)))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted },
|
|
23205
|
-
}
|
|
23206
|
-
function AddOverlay({ title, columnName }) {
|
|
23207
|
-
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, `add to ${columnName}`), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: title ? PALETTE.text : PALETTE.textMuted }, title || "title\u2026"), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.accent }, GLYPHS.cursor))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "\u21B5 create \xB7 esc cancel")));
|
|
23208
|
-
}
|
|
23209
|
-
|
|
23210
|
-
// src/tui/components/RulesPanel.tsx
|
|
23211
|
-
var import_react43 = __toESM(require_react());
|
|
23212
|
-
var RULE_TYPES2 = ["always", "never", "prefer", "context"];
|
|
23213
|
-
var RULE_TYPE_LABELS = {
|
|
23214
|
-
always: "Always",
|
|
23215
|
-
never: "Never",
|
|
23216
|
-
prefer: "Prefer",
|
|
23217
|
-
context: "Context"
|
|
23218
|
-
};
|
|
23219
|
-
var RULE_TYPE_COLORS = {
|
|
23220
|
-
always: PALETTE.success,
|
|
23221
|
-
never: PALETTE.error,
|
|
23222
|
-
prefer: PALETTE.warning,
|
|
23223
|
-
context: PALETTE.accent
|
|
23224
|
-
};
|
|
23225
|
-
function RulesPanel({
|
|
23226
|
-
rules,
|
|
23227
|
-
activeRuleType,
|
|
23228
|
-
selectedRuleIndex,
|
|
23229
|
-
viewportHeight,
|
|
23230
|
-
termWidth,
|
|
23231
|
-
mode,
|
|
23232
|
-
editText,
|
|
23233
|
-
layoutMode = "wide"
|
|
23234
|
-
}) {
|
|
23235
|
-
const currentRules = rules?.[activeRuleType] || [];
|
|
23236
|
-
const maxWidth = Math.max(termWidth - 8, 20);
|
|
23237
|
-
const scrollPadding = 2;
|
|
23238
|
-
const headerRows = layoutMode === "narrow" ? 4 : 6;
|
|
23239
|
-
const visibleRules = Math.max(viewportHeight - headerRows, 3);
|
|
23240
|
-
let scrollOffset = 0;
|
|
23241
|
-
if (selectedRuleIndex >= visibleRules - scrollPadding) {
|
|
23242
|
-
scrollOffset = Math.min(
|
|
23243
|
-
selectedRuleIndex - visibleRules + scrollPadding + 1,
|
|
23244
|
-
Math.max(0, currentRules.length - visibleRules)
|
|
23245
|
-
);
|
|
23246
|
-
}
|
|
23247
|
-
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", paddingX: 1 }, layoutMode === "narrow" ? /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginBottom: 1 }, RULE_TYPES2.map((type) => {
|
|
23248
|
-
const count = rules?.[type]?.length || 0;
|
|
23249
|
-
const isActive = type === activeRuleType;
|
|
23250
|
-
const initial = RULE_TYPE_LABELS[type].charAt(0);
|
|
23251
|
-
return /* @__PURE__ */ import_react43.default.createElement(Text, { key: type }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23252
|
-
Text,
|
|
23253
|
-
{
|
|
23254
|
-
color: isActive ? RULE_TYPE_COLORS[type] : PALETTE.textDim,
|
|
23255
|
-
bold: isActive
|
|
23256
|
-
},
|
|
23257
|
-
initial
|
|
23258
|
-
), count > 0 && /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textDim }, "(", count, ")"), /* @__PURE__ */ import_react43.default.createElement(Text, null, " "));
|
|
23259
|
-
}), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textDim }, "h/l")) : /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginBottom: 1 }, RULE_TYPES2.map((type, idx) => {
|
|
23260
|
-
const count = rules?.[type]?.length || 0;
|
|
23261
|
-
const isActive = type === activeRuleType;
|
|
23262
|
-
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { key: type, marginRight: 2 }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23263
|
-
Text,
|
|
23264
|
-
{
|
|
23265
|
-
color: isActive ? RULE_TYPE_COLORS[type] : PALETTE.textMuted,
|
|
23266
|
-
bold: isActive,
|
|
23267
|
-
inverse: isActive
|
|
23268
|
-
},
|
|
23269
|
-
" ",
|
|
23270
|
-
RULE_TYPE_LABELS[type],
|
|
23271
|
-
count > 0 && /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, " (", count, ")"),
|
|
23272
|
-
" "
|
|
23273
|
-
), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textDim }, " [", idx + 1, "]"));
|
|
23274
|
-
})), layoutMode === "wide" && /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginBottom: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "h/l"), " switch type", " ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "j/k"), " select", " ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "n"), " new", " ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "e"), " edit", " ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "d"), " delete")), /* @__PURE__ */ import_react43.default.createElement(Box_default, null, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.border }, RULE.repeat(Math.max(1, termWidth - 4)))), (mode === "rule-add" || mode === "rule-edit") && /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.accent }, mode === "rule-add" ? "New rule:" : "Edit rule:"), /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginTop: 0 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.text, backgroundColor: PALETTE.bgHighlight }, " ", editText || " ", GLYPHS.cursor, " ")), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted, dimColor: true }, "Enter to save, Esc to cancel")), mode === "rule-delete-confirm" && currentRules[selectedRuleIndex] && /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.error, bold: true }, "Delete this ", activeRuleType, " rule?"), /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, "#", currentRules[selectedRuleIndex].id, ": ", truncate(currentRules[selectedRuleIndex].rule, maxWidth - 10))), /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, "Press ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.success }, "y"), " to confirm, ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.error }, "n"), " to cancel"))), mode !== "rule-add" && mode !== "rule-edit" && mode !== "rule-delete-confirm" && /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", marginTop: 1 }, currentRules.length === 0 ? /* @__PURE__ */ import_react43.default.createElement(Box_default, { paddingY: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, "No ", activeRuleType, " rules defined. Press ", /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.accent }, "n"), " to add one.")) : currentRules.slice(scrollOffset, scrollOffset + visibleRules).map((rule, displayIdx) => {
|
|
23275
|
-
const actualIdx = scrollOffset + displayIdx;
|
|
23276
|
-
const isSelected = actualIdx === selectedRuleIndex;
|
|
23277
|
-
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { key: rule.id, paddingY: 0 }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23278
|
-
Text,
|
|
23279
|
-
{
|
|
23280
|
-
color: isSelected ? PALETTE.text : PALETTE.textSecondary,
|
|
23281
|
-
backgroundColor: isSelected ? PALETTE.bgHighlight : void 0,
|
|
23282
|
-
bold: isSelected
|
|
23283
|
-
},
|
|
23284
|
-
isSelected ? GLYPHS.pointer : " ",
|
|
23285
|
-
" ",
|
|
23286
|
-
/* @__PURE__ */ import_react43.default.createElement(Text, { color: RULE_TYPE_COLORS[activeRuleType] }, "#", rule.id),
|
|
23287
|
-
" ",
|
|
23288
|
-
truncate(rule.rule, maxWidth - 8)
|
|
23289
|
-
));
|
|
23290
|
-
}), currentRules.length > visibleRules && /* @__PURE__ */ import_react43.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textDim }, scrollOffset + 1, "-", Math.min(scrollOffset + visibleRules, currentRules.length), " of ", currentRules.length))));
|
|
22921
|
+
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.warning }, "complete"), /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, ` ${target.id} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.text }, truncate(target.title, Math.max(1, width - target.id.length - 6)))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, incompleteChildrenLabel(target.incompleteChildren.length))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 4 }, target.incompleteChildren.map((child) => /* @__PURE__ */ import_react42.default.createElement(Text, { key: child.id, wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, `${child.id} `), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textSecondary }, truncate(child.title, Math.max(1, width - child.id.length - 8)))))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, "y complete anyway \xB7 n cancel")));
|
|
23291
22922
|
}
|
|
23292
|
-
|
|
23293
|
-
|
|
23294
|
-
var import_react44 = __toESM(require_react());
|
|
23295
|
-
function LogsPanel({
|
|
23296
|
-
logs,
|
|
23297
|
-
selectedIndex,
|
|
23298
|
-
viewportHeight,
|
|
23299
|
-
termWidth,
|
|
23300
|
-
expandedIds,
|
|
23301
|
-
mode,
|
|
23302
|
-
columns,
|
|
23303
|
-
restoreColumnIndex,
|
|
23304
|
-
layoutMode = "wide"
|
|
23305
|
-
}) {
|
|
23306
|
-
const maxWidth = Math.max(termWidth - 8, 20);
|
|
23307
|
-
const scrollPadding = 2;
|
|
23308
|
-
const headerRows = layoutMode === "narrow" ? 4 : 6;
|
|
23309
|
-
const visibleTasks = Math.max(viewportHeight - headerRows, 3);
|
|
23310
|
-
let scrollOffset = 0;
|
|
23311
|
-
if (selectedIndex >= visibleTasks - scrollPadding) {
|
|
23312
|
-
scrollOffset = Math.min(
|
|
23313
|
-
selectedIndex - visibleTasks + scrollPadding + 1,
|
|
23314
|
-
Math.max(0, logs.length - visibleTasks)
|
|
23315
|
-
);
|
|
23316
|
-
}
|
|
23317
|
-
const selectedTask = logs[selectedIndex];
|
|
23318
|
-
return /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginBottom: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, logs.length, " logged", layoutMode === "wide" ? ` task${logs.length !== 1 ? "s" : ""}` : ""), layoutMode === "wide" && /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginLeft: 2 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, "j/k"), " select", " ", /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, "Enter"), " expand", " ", /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, "R"), " restore", " ", /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, "d"), " delete"))), /* @__PURE__ */ import_react44.default.createElement(Box_default, null, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.border }, RULE.repeat(Math.max(1, termWidth - 4)))), mode === "logs-restore" && selectedTask && /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.accent }, 'Restore "', /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.text }, truncate(selectedTask.title, 30)), '" to:'), /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", marginTop: 1 }, columns.map((col, idx) => /* @__PURE__ */ import_react44.default.createElement(Box_default, { key: col.id }, /* @__PURE__ */ import_react44.default.createElement(
|
|
23319
|
-
Text,
|
|
23320
|
-
{
|
|
23321
|
-
color: idx === restoreColumnIndex ? PALETTE.text : PALETTE.textMuted,
|
|
23322
|
-
backgroundColor: idx === restoreColumnIndex ? PALETTE.bgHighlight : void 0,
|
|
23323
|
-
bold: idx === restoreColumnIndex
|
|
23324
|
-
},
|
|
23325
|
-
idx === restoreColumnIndex ? GLYPHS.pointer : " ",
|
|
23326
|
-
" ",
|
|
23327
|
-
col.title,
|
|
23328
|
-
/* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textDim }, " (", col.tasks?.length ?? 0, ")")
|
|
23329
|
-
)))), /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, "j/k to select, Enter to restore, Esc to cancel"))), mode === "logs-delete-confirm" && selectedTask && /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", marginY: 1, paddingX: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.error, bold: true }, "Permanently delete this logged task?"), /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, selectedTask.id, ": ", truncate(selectedTask.title, maxWidth - 15))), /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.warning }, "This cannot be undone.")), /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, "Press ", /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.success }, "y"), " to confirm, ", /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.error }, "n"), " to cancel"))), mode !== "logs-restore" && mode !== "logs-delete-confirm" && /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", marginTop: 1 }, logs.length === 0 ? /* @__PURE__ */ import_react44.default.createElement(Box_default, { paddingY: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, "No logged tasks.")) : logs.slice(scrollOffset, scrollOffset + visibleTasks).map((task, displayIdx) => {
|
|
23330
|
-
const actualIdx = scrollOffset + displayIdx;
|
|
23331
|
-
const isSelected = actualIdx === selectedIndex;
|
|
23332
|
-
const isExpanded = expandedIds.has(task.id);
|
|
23333
|
-
return /* @__PURE__ */ import_react44.default.createElement(Box_default, { key: task.id, flexDirection: "column", paddingY: 0 }, /* @__PURE__ */ import_react44.default.createElement(Box_default, null, /* @__PURE__ */ import_react44.default.createElement(
|
|
23334
|
-
Text,
|
|
23335
|
-
{
|
|
23336
|
-
color: isSelected ? PALETTE.text : PALETTE.textSecondary,
|
|
23337
|
-
backgroundColor: isSelected ? PALETTE.bgHighlight : void 0,
|
|
23338
|
-
bold: isSelected
|
|
23339
|
-
},
|
|
23340
|
-
isSelected ? GLYPHS.pointer : " ",
|
|
23341
|
-
" ",
|
|
23342
|
-
isExpanded ? "\u25BC" : GLYPHS.collapsed,
|
|
23343
|
-
" ",
|
|
23344
|
-
/* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textDim }, task.id),
|
|
23345
|
-
" ",
|
|
23346
|
-
truncate(task.title, maxWidth - task.id.length - 8),
|
|
23347
|
-
task.priority && /* @__PURE__ */ import_react44.default.createElement(Text, { color: getPriorityColor(task.priority) }, " [", task.priority, "]")
|
|
23348
|
-
)), isExpanded && /* @__PURE__ */ import_react44.default.createElement(Box_default, { flexDirection: "column", marginLeft: 4, marginBottom: 1 }, task.description && /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, truncate(task.description, maxWidth - 6)), task.tags && task.tags.length > 0 && /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.accent }, task.tags.map((t) => `#${t}`).join(" ")), task.assignee && /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textSecondary }, "@", task.assignee), task.subtasks && task.subtasks.length > 0 && /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textMuted }, "Subtasks: ", task.subtasks.filter((s) => s.completed).length, "/", task.subtasks.length)));
|
|
23349
|
-
}), logs.length > visibleTasks && /* @__PURE__ */ import_react44.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react44.default.createElement(Text, { color: PALETTE.textDim }, scrollOffset + 1, "-", Math.min(scrollOffset + visibleTasks, logs.length), " of ", logs.length))));
|
|
22923
|
+
function AddOverlay({ title, columnName, thenEdit = false }) {
|
|
22924
|
+
return /* @__PURE__ */ import_react42.default.createElement(Box_default, { flexDirection: "column", flexGrow: 1, paddingLeft: 1 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, thenEdit ? `add to ${columnName}, then edit` : `add to ${columnName}`), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { wrap: "truncate" }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: title ? PALETTE.text : PALETTE.textMuted }, title || "title\u2026"), /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.accent }, GLYPHS.cursor))), /* @__PURE__ */ import_react42.default.createElement(Box_default, { marginTop: 1, paddingLeft: 2 }, /* @__PURE__ */ import_react42.default.createElement(Text, { color: PALETTE.textMuted }, thenEdit ? "\u21B5 create and edit \xB7 esc cancel" : "\u21B5 create \xB7 esc cancel")));
|
|
23350
22925
|
}
|
|
23351
22926
|
|
|
23352
22927
|
// src/tui/BrainfileTUI.tsx
|
|
23353
|
-
var
|
|
22928
|
+
var baseInitialState = {
|
|
23354
22929
|
board: null,
|
|
23355
22930
|
error: null,
|
|
23356
22931
|
lastUpdated: /* @__PURE__ */ new Date(),
|
|
23357
|
-
activePanel: "board",
|
|
23358
22932
|
activeColumnIndex: 0,
|
|
23359
22933
|
selectedTaskIndex: 0,
|
|
23360
22934
|
mode: "browse",
|
|
22935
|
+
helpReturn: null,
|
|
23361
22936
|
filterQuery: "",
|
|
23362
|
-
|
|
22937
|
+
activeTypeFilter: "all",
|
|
22938
|
+
doneView: false,
|
|
22939
|
+
detailPath: [],
|
|
22940
|
+
detailCursor: 0,
|
|
22941
|
+
detailScroll: 0,
|
|
23363
22942
|
moveTargetIndex: 0,
|
|
23364
22943
|
newTaskTitle: "",
|
|
22944
|
+
addThenEdit: false,
|
|
23365
22945
|
completeConfirm: null,
|
|
23366
22946
|
statusMessage: null,
|
|
23367
22947
|
lastContentHash: null,
|
|
23368
22948
|
reloadFlash: false,
|
|
23369
|
-
|
|
23370
|
-
selectedRuleIndex: 0,
|
|
23371
|
-
ruleEditText: "",
|
|
23372
|
-
ruleEditId: null,
|
|
23373
|
-
logs: [],
|
|
23374
|
-
selectedLogIndex: 0,
|
|
23375
|
-
logRestoreColumnIndex: 0,
|
|
23376
|
-
expandedLogIds: /* @__PURE__ */ new Set()
|
|
22949
|
+
logs: []
|
|
23377
22950
|
};
|
|
23378
22951
|
function BrainfileTUI({ filePath, width, height }) {
|
|
23379
22952
|
const { stdout } = use_stdout_default();
|
|
23380
|
-
const [dimensions, setDimensions] = (0,
|
|
22953
|
+
const [dimensions, setDimensions] = (0, import_react43.useState)({
|
|
23381
22954
|
width: width ?? stdout?.columns ?? process.stdout.columns ?? 80,
|
|
23382
22955
|
height: height ?? stdout?.rows ?? process.stdout.rows ?? 24
|
|
23383
22956
|
});
|
|
23384
|
-
(0,
|
|
22957
|
+
(0, import_react43.useEffect)(() => {
|
|
23385
22958
|
if (width !== void 0 && height !== void 0) return void 0;
|
|
23386
22959
|
const handleResize = () => {
|
|
23387
22960
|
setDimensions({
|
|
@@ -23398,10 +22971,20 @@ function BrainfileTUI({ filePath, width, height }) {
|
|
|
23398
22971
|
const termHeight = height ?? dimensions.height;
|
|
23399
22972
|
const isTooSmall = termWidth < LAYOUT.MIN_WIDTH || termHeight < LAYOUT.MIN_HEIGHT;
|
|
23400
22973
|
const layoutMode = termWidth >= LAYOUT.DETAIL_PANE_MIN_WIDTH ? "wide" : "narrow";
|
|
23401
|
-
const
|
|
22974
|
+
const resumeColumn = (0, import_react43.useRef)(void 0);
|
|
22975
|
+
const [state, setState] = (0, import_react43.useState)(() => {
|
|
22976
|
+
const persisted = readTuiState(filePath);
|
|
22977
|
+
resumeColumn.current = persisted.lastColumn;
|
|
22978
|
+
return {
|
|
22979
|
+
...baseInitialState,
|
|
22980
|
+
collapsedIds: new Set(persisted.collapsed),
|
|
22981
|
+
// 'done' was a pseudo-type before the L toggle; never resume into it.
|
|
22982
|
+
activeTypeFilter: persisted.lastTypeFilter && persisted.lastTypeFilter !== "done" ? persisted.lastTypeFilter : baseInitialState.activeTypeFilter
|
|
22983
|
+
};
|
|
22984
|
+
});
|
|
23402
22985
|
const viewportHeight = Math.max(termHeight - HEADER_ROWS - FOOTER_ROWS, 3);
|
|
23403
22986
|
const { loadBrainfile } = useBrainfileLoader(filePath, state, setState);
|
|
23404
|
-
const orderedColumns = (0,
|
|
22987
|
+
const orderedColumns = (0, import_react43.useMemo)(() => {
|
|
23405
22988
|
if (!state.board) return [];
|
|
23406
22989
|
return [...state.board.columns].sort((a, b) => {
|
|
23407
22990
|
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -23409,51 +22992,102 @@ function BrainfileTUI({ filePath, width, height }) {
|
|
|
23409
22992
|
return orderA - orderB;
|
|
23410
22993
|
});
|
|
23411
22994
|
}, [state.board]);
|
|
23412
|
-
const
|
|
23413
|
-
|
|
23414
|
-
|
|
22995
|
+
const typeCycleOptions = (0, import_react43.useMemo)(() => getTypeCycleOptions(state.board), [state.board]);
|
|
22996
|
+
const activeTypeFilter = typeCycleOptions.includes(state.activeTypeFilter) ? state.activeTypeFilter : "all";
|
|
22997
|
+
const typeFilterActive = activeTypeFilter !== "all";
|
|
22998
|
+
const doneView = state.doneView;
|
|
22999
|
+
const typeFilteredColumns = (0, import_react43.useMemo)(() => {
|
|
23000
|
+
if (!typeFilterActive) return orderedColumns;
|
|
23001
|
+
return orderedColumns.map((column) => ({
|
|
23002
|
+
...column,
|
|
23003
|
+
tasks: (column.tasks ?? []).filter((task) => getDocType(task) === activeTypeFilter)
|
|
23004
|
+
}));
|
|
23005
|
+
}, [orderedColumns, typeFilterActive, doneView, activeTypeFilter]);
|
|
23006
|
+
const totalCount = (0, import_react43.useMemo)(
|
|
23007
|
+
() => doneView ? typeFilterActive ? state.logs.filter((task) => getDocType(task) === activeTypeFilter).length : state.logs.length : typeFilteredColumns.reduce((sum, column) => sum + (column.tasks?.length ?? 0), 0),
|
|
23008
|
+
[typeFilteredColumns, doneView, state.logs]
|
|
23415
23009
|
);
|
|
23416
23010
|
const filterQuery = state.filterQuery.trim();
|
|
23417
23011
|
const hasFilter = filterQuery.length > 0;
|
|
23418
|
-
const filteredColumns = (0,
|
|
23419
|
-
if (!hasFilter) return
|
|
23420
|
-
return
|
|
23012
|
+
const filteredColumns = (0, import_react43.useMemo)(() => {
|
|
23013
|
+
if (!hasFilter) return typeFilteredColumns;
|
|
23014
|
+
return typeFilteredColumns.map((column) => {
|
|
23421
23015
|
const docs = (column.tasks ?? []).map((task) => ({ task, body: task.description ?? "" }));
|
|
23422
|
-
const matches = (0,
|
|
23016
|
+
const matches = (0, import_core6.searchTasksRanked)(docs, filterQuery);
|
|
23423
23017
|
return { ...column, tasks: matches.map((match) => match.doc.task) };
|
|
23424
23018
|
});
|
|
23425
|
-
}, [
|
|
23426
|
-
const
|
|
23427
|
-
|
|
23428
|
-
|
|
23019
|
+
}, [typeFilteredColumns, hasFilter, filterQuery]);
|
|
23020
|
+
const filteredLogs = (0, import_react43.useMemo)(() => {
|
|
23021
|
+
if (!doneView) return [];
|
|
23022
|
+
const typed = typeFilterActive ? state.logs.filter((task) => getDocType(task) === activeTypeFilter) : state.logs;
|
|
23023
|
+
if (!hasFilter) return typed;
|
|
23024
|
+
const docs = typed.map((task) => ({ task, body: task.description ?? "" }));
|
|
23025
|
+
return (0, import_core6.searchTasksRanked)(docs, filterQuery).map((match) => match.doc.task);
|
|
23026
|
+
}, [doneView, state.logs, typeFilterActive, activeTypeFilter, hasFilter, filterQuery]);
|
|
23027
|
+
const matchCount = (0, import_react43.useMemo)(
|
|
23028
|
+
() => doneView ? filteredLogs.length : filteredColumns.reduce((sum, column) => sum + (column.tasks?.length ?? 0), 0),
|
|
23029
|
+
[filteredColumns, doneView, filteredLogs]
|
|
23429
23030
|
);
|
|
23430
23031
|
const activeColumnIndex = Math.min(
|
|
23431
23032
|
state.activeColumnIndex,
|
|
23432
23033
|
Math.max(0, filteredColumns.length - 1)
|
|
23433
23034
|
);
|
|
23434
23035
|
const currentColumn = filteredColumns[activeColumnIndex];
|
|
23435
|
-
const rows = (0,
|
|
23036
|
+
const rows = (0, import_react43.useMemo)(
|
|
23037
|
+
() => doneView ? buildFlatRows(filteredLogs) : typeFilterActive ? buildFlatRows(currentColumn?.tasks ?? []) : buildRows(currentColumn?.tasks ?? [], state.collapsedIds),
|
|
23038
|
+
[currentColumn, doneView, filteredLogs, typeFilterActive, state.collapsedIds]
|
|
23039
|
+
);
|
|
23436
23040
|
const maxRowIndex = Math.max(0, rows.length - 1);
|
|
23437
23041
|
const selectedIndex = Math.min(state.selectedTaskIndex, maxRowIndex);
|
|
23438
23042
|
const selectedTask = rows[selectedIndex]?.task;
|
|
23439
|
-
const allBoardTasks = (0,
|
|
23043
|
+
const allBoardTasks = (0, import_react43.useMemo)(
|
|
23440
23044
|
() => orderedColumns.flatMap((column) => column.tasks ?? []),
|
|
23441
23045
|
[orderedColumns]
|
|
23442
23046
|
);
|
|
23443
|
-
const
|
|
23444
|
-
|
|
23047
|
+
const allDocs = (0, import_react43.useMemo)(
|
|
23048
|
+
() => [...allBoardTasks, ...state.logs],
|
|
23049
|
+
[allBoardTasks, state.logs]
|
|
23050
|
+
);
|
|
23051
|
+
const archivedIds = (0, import_react43.useMemo)(() => new Set(state.logs.map((t) => t.id)), [state.logs]);
|
|
23052
|
+
const detailTaskId = state.detailPath[state.detailPath.length - 1];
|
|
23053
|
+
const detailTask = state.detailPath.length > 0 ? allDocs.find((t) => t.id === detailTaskId) : void 0;
|
|
23054
|
+
const detailChildren = (0, import_react43.useMemo)(
|
|
23055
|
+
() => detailTask ? allDocs.filter((t) => t.parentId === detailTask.id) : [],
|
|
23056
|
+
[allDocs, detailTask]
|
|
23057
|
+
);
|
|
23058
|
+
const detailParent = detailTask?.parentId ? allDocs.find((t) => t.id === detailTask.parentId) : void 0;
|
|
23059
|
+
const detailColumnLabel = (0, import_react43.useMemo)(() => {
|
|
23060
|
+
if (!detailTask) return "";
|
|
23061
|
+
if (archivedIds.has(detailTask.id)) {
|
|
23062
|
+
const at = detailTask.completedAt;
|
|
23063
|
+
return at ? `completed ${at.slice(5, 10)}` : "completed";
|
|
23064
|
+
}
|
|
23065
|
+
return orderedColumns.find((c) => c.tasks?.some((t) => t.id === detailTask.id))?.id ?? "";
|
|
23066
|
+
}, [orderedColumns, detailTask, archivedIds]);
|
|
23067
|
+
const detailActivity = (0, import_react43.useMemo)(
|
|
23068
|
+
() => detailTask ? getTaskActivity(filePath, detailTask.id) : [],
|
|
23069
|
+
[detailTask, filePath, state.lastUpdated]
|
|
23070
|
+
);
|
|
23071
|
+
const detailOpen = state.mode === "detail" && Boolean(detailTask);
|
|
23072
|
+
const fullscreenDetailMode = detailOpen && layoutMode === "narrow";
|
|
23073
|
+
const detailPaneWidth = Math.floor(termWidth * LAYOUT.DETAIL_PANE_FRACTION);
|
|
23074
|
+
const detailViewWidth = fullscreenDetailMode ? termWidth : detailPaneWidth;
|
|
23075
|
+
const detailViewHeight = fullscreenDetailMode ? termHeight - FOOTER_ROWS : viewportHeight;
|
|
23076
|
+
const detailLayout = detailTask ? computeDetailLayout(detailTask, detailViewWidth, detailViewHeight, detailChildren, detailActivity) : null;
|
|
23077
|
+
(0, import_react43.useEffect)(() => {
|
|
23078
|
+
const wanted = resumeColumn.current;
|
|
23079
|
+
if (!wanted || orderedColumns.length === 0) return;
|
|
23080
|
+
resumeColumn.current = void 0;
|
|
23081
|
+
const idx = orderedColumns.findIndex((c) => c.id === wanted);
|
|
23082
|
+
if (idx > 0) setState((prev) => ({ ...prev, activeColumnIndex: idx }));
|
|
23083
|
+
}, [orderedColumns]);
|
|
23084
|
+
(0, import_react43.useEffect)(() => {
|
|
23445
23085
|
setState((prev) => ({
|
|
23446
23086
|
...prev,
|
|
23447
23087
|
activeColumnIndex: Math.min(prev.activeColumnIndex, Math.max(0, filteredColumns.length - 1)),
|
|
23448
23088
|
selectedTaskIndex: Math.min(prev.selectedTaskIndex, maxRowIndex)
|
|
23449
23089
|
}));
|
|
23450
23090
|
}, [filteredColumns.length, maxRowIndex]);
|
|
23451
|
-
(0, import_react45.useEffect)(() => {
|
|
23452
|
-
if (state.activePanel === "logs") {
|
|
23453
|
-
const result = loadLogs(filePath);
|
|
23454
|
-
setState((prev) => ({ ...prev, logs: result.logs }));
|
|
23455
|
-
}
|
|
23456
|
-
}, [state.activePanel, filePath, state.lastUpdated]);
|
|
23457
23091
|
useKeyboardNavigation({
|
|
23458
23092
|
state: { ...state, activeColumnIndex, selectedTaskIndex: selectedIndex },
|
|
23459
23093
|
setState,
|
|
@@ -23462,42 +23096,69 @@ function BrainfileTUI({ filePath, width, height }) {
|
|
|
23462
23096
|
viewportHeight,
|
|
23463
23097
|
loadBrainfile,
|
|
23464
23098
|
filePath,
|
|
23465
|
-
allColumns: orderedColumns
|
|
23099
|
+
allColumns: orderedColumns,
|
|
23100
|
+
typeCycleOptions,
|
|
23101
|
+
detailTask,
|
|
23102
|
+
detailChildren,
|
|
23103
|
+
detailParent,
|
|
23104
|
+
detailActivity,
|
|
23105
|
+
detailWidth: detailViewWidth,
|
|
23106
|
+
detailHeight: detailViewHeight,
|
|
23107
|
+
doneView,
|
|
23108
|
+
detailArchived: Boolean(detailTask && archivedIds.has(detailTask.id))
|
|
23466
23109
|
});
|
|
23467
23110
|
if (isTooSmall) {
|
|
23468
|
-
return /* @__PURE__ */
|
|
23111
|
+
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", width: termWidth, height: termHeight, paddingLeft: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.error }, CHROME.tooSmall), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, "Minimum ", LAYOUT.MIN_WIDTH, "\xD7", LAYOUT.MIN_HEIGHT, " \xB7 current ", termWidth, "\xD7", termHeight));
|
|
23469
23112
|
}
|
|
23470
23113
|
if (state.error) {
|
|
23471
|
-
return /* @__PURE__ */
|
|
23114
|
+
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", paddingLeft: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.error }, CHROME.errorTitle), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textSecondary }, state.error), /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, CHROME.errorHint));
|
|
23472
23115
|
}
|
|
23473
23116
|
if (!state.board) {
|
|
23474
|
-
return /* @__PURE__ */
|
|
23117
|
+
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { paddingLeft: 1 }, /* @__PURE__ */ import_react43.default.createElement(Text, { color: PALETTE.textMuted }, CHROME.loading));
|
|
23475
23118
|
}
|
|
23476
23119
|
if (state.mode === "help") {
|
|
23477
|
-
return /* @__PURE__ */
|
|
23120
|
+
return /* @__PURE__ */ import_react43.default.createElement(HelpOverlay, { termWidth, termHeight });
|
|
23478
23121
|
}
|
|
23479
23122
|
const boardTitle = state.board.title || "brainfile";
|
|
23480
|
-
const columnLabel = currentColumn?.id ?? "";
|
|
23123
|
+
const columnLabel = doneView ? "done" : currentColumn?.id ?? "";
|
|
23481
23124
|
const columnName = currentColumn?.title ?? "";
|
|
23482
|
-
const detailOpen = state.mode === "detail" && Boolean(selectedTask);
|
|
23483
23125
|
const fullscreenDetail = detailOpen && layoutMode === "narrow";
|
|
23484
|
-
|
|
23485
|
-
|
|
23126
|
+
const detailArchived = Boolean(detailTask && archivedIds.has(detailTask.id));
|
|
23127
|
+
const detailFooterCtx = {
|
|
23128
|
+
task: detailTask,
|
|
23129
|
+
hasParent: Boolean(detailParent),
|
|
23130
|
+
hasChildren: detailChildren.length > 0,
|
|
23131
|
+
hasSubtasks: (detailTask?.subtasks?.length ?? 0) > 0,
|
|
23132
|
+
bodyOverflows: detailLayout?.bodyOverflows ?? false,
|
|
23133
|
+
archived: detailArchived
|
|
23134
|
+
};
|
|
23135
|
+
if (fullscreenDetail && detailTask) {
|
|
23136
|
+
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", width: termWidth, height: termHeight }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23486
23137
|
DetailView,
|
|
23487
23138
|
{
|
|
23488
|
-
task:
|
|
23489
|
-
columnLabel,
|
|
23139
|
+
task: detailTask,
|
|
23140
|
+
columnLabel: detailColumnLabel,
|
|
23490
23141
|
width: termWidth,
|
|
23491
23142
|
height: termHeight - FOOTER_ROWS,
|
|
23492
|
-
|
|
23493
|
-
parent:
|
|
23143
|
+
breadcrumb: state.detailPath,
|
|
23144
|
+
parent: detailParent,
|
|
23145
|
+
children: detailChildren,
|
|
23146
|
+
activity: detailActivity,
|
|
23147
|
+
cursor: state.detailCursor,
|
|
23148
|
+
scrollOffset: state.detailScroll
|
|
23149
|
+
}
|
|
23150
|
+
), /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexGrow: 1 }), /* @__PURE__ */ import_react43.default.createElement(Box_default, { height: 1, flexShrink: 0 }, /* @__PURE__ */ import_react43.default.createElement(StatusMessageDisplay, { message: state.statusMessage })), /* @__PURE__ */ import_react43.default.createElement(
|
|
23151
|
+
FooterBar,
|
|
23152
|
+
{
|
|
23153
|
+
width: termWidth,
|
|
23154
|
+
actions: detailActions(detailFooterCtx),
|
|
23155
|
+
stateChip: detailColumnLabel
|
|
23494
23156
|
}
|
|
23495
|
-
)
|
|
23157
|
+
));
|
|
23496
23158
|
}
|
|
23497
|
-
const detailPaneWidth = Math.floor(termWidth * LAYOUT.DETAIL_PANE_FRACTION);
|
|
23498
23159
|
const listWidth = detailOpen ? termWidth - detailPaneWidth : termWidth;
|
|
23499
|
-
const footerActions = detailOpen ? detailActions(
|
|
23500
|
-
return /* @__PURE__ */
|
|
23160
|
+
const footerActions = detailOpen ? detailActions(detailFooterCtx) : browseActions(selectedTask, doneView);
|
|
23161
|
+
return /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "column", width: termWidth, height: termHeight }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23501
23162
|
HeaderBar,
|
|
23502
23163
|
{
|
|
23503
23164
|
title: boardTitle,
|
|
@@ -23508,9 +23169,10 @@ function BrainfileTUI({ filePath, width, height }) {
|
|
|
23508
23169
|
filterActive: state.mode === "filter" || hasFilter,
|
|
23509
23170
|
matchCount,
|
|
23510
23171
|
totalCount,
|
|
23511
|
-
panelLabel:
|
|
23172
|
+
panelLabel: doneView ? "done" : void 0,
|
|
23173
|
+
activeType: typeFilterActive ? activeTypeFilter : "all"
|
|
23512
23174
|
}
|
|
23513
|
-
), /* @__PURE__ */
|
|
23175
|
+
), /* @__PURE__ */ import_react43.default.createElement(Box_default, { flexGrow: 1, flexShrink: 0, flexDirection: "column" }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23514
23176
|
BoardContent,
|
|
23515
23177
|
{
|
|
23516
23178
|
state,
|
|
@@ -23521,45 +23183,26 @@ function BrainfileTUI({ filePath, width, height }) {
|
|
|
23521
23183
|
detailPaneWidth,
|
|
23522
23184
|
detailOpen,
|
|
23523
23185
|
selectedTask,
|
|
23524
|
-
|
|
23525
|
-
|
|
23186
|
+
detailTask,
|
|
23187
|
+
detailParent,
|
|
23188
|
+
detailChildren,
|
|
23189
|
+
detailActivity,
|
|
23190
|
+
detailColumnLabel,
|
|
23526
23191
|
columnName,
|
|
23527
23192
|
columns: orderedColumns,
|
|
23528
23193
|
termWidth,
|
|
23529
|
-
hasFilter
|
|
23530
|
-
|
|
23531
|
-
|
|
23532
|
-
|
|
23533
|
-
{
|
|
23534
|
-
rules: state.board.rules,
|
|
23535
|
-
activeRuleType: state.activeRuleType,
|
|
23536
|
-
selectedRuleIndex: state.selectedRuleIndex,
|
|
23537
|
-
viewportHeight,
|
|
23538
|
-
termWidth,
|
|
23539
|
-
mode: state.mode,
|
|
23540
|
-
editText: state.ruleEditText,
|
|
23541
|
-
layoutMode
|
|
23542
|
-
}
|
|
23543
|
-
) : null, state.activePanel === "logs" ? /* @__PURE__ */ import_react45.default.createElement(
|
|
23544
|
-
LogsPanel,
|
|
23545
|
-
{
|
|
23546
|
-
logs: state.logs,
|
|
23547
|
-
selectedIndex: state.selectedLogIndex,
|
|
23548
|
-
viewportHeight,
|
|
23549
|
-
termWidth,
|
|
23550
|
-
expandedIds: state.expandedLogIds,
|
|
23551
|
-
mode: state.mode,
|
|
23552
|
-
columns: orderedColumns,
|
|
23553
|
-
restoreColumnIndex: state.logRestoreColumnIndex,
|
|
23554
|
-
layoutMode
|
|
23194
|
+
hasFilter,
|
|
23195
|
+
archived: doneView,
|
|
23196
|
+
typeFilter: typeFilterActive ? activeTypeFilter : void 0,
|
|
23197
|
+
thenEdit: state.addThenEdit
|
|
23555
23198
|
}
|
|
23556
|
-
)
|
|
23199
|
+
)), /* @__PURE__ */ import_react43.default.createElement(Box_default, { height: 1, flexShrink: 0 }, /* @__PURE__ */ import_react43.default.createElement(StatusMessageDisplay, { message: state.statusMessage })), /* @__PURE__ */ import_react43.default.createElement(
|
|
23557
23200
|
FooterBar,
|
|
23558
23201
|
{
|
|
23559
23202
|
width: termWidth,
|
|
23560
|
-
itemCount:
|
|
23561
|
-
actions:
|
|
23562
|
-
stateChip:
|
|
23203
|
+
itemCount: detailOpen ? void 0 : rows.length,
|
|
23204
|
+
actions: footerActions,
|
|
23205
|
+
stateChip: columnLabel
|
|
23563
23206
|
}
|
|
23564
23207
|
));
|
|
23565
23208
|
}
|
|
@@ -23572,80 +23215,96 @@ function BoardContent({
|
|
|
23572
23215
|
detailPaneWidth,
|
|
23573
23216
|
detailOpen,
|
|
23574
23217
|
selectedTask,
|
|
23575
|
-
|
|
23576
|
-
|
|
23218
|
+
detailTask,
|
|
23219
|
+
detailParent,
|
|
23220
|
+
detailChildren,
|
|
23221
|
+
detailActivity,
|
|
23222
|
+
detailColumnLabel,
|
|
23577
23223
|
columnName,
|
|
23578
23224
|
columns,
|
|
23579
23225
|
termWidth,
|
|
23580
|
-
hasFilter
|
|
23226
|
+
hasFilter,
|
|
23227
|
+
archived,
|
|
23228
|
+
typeFilter,
|
|
23229
|
+
thenEdit
|
|
23581
23230
|
}) {
|
|
23582
|
-
|
|
23583
|
-
|
|
23231
|
+
const overlayTarget = state.detailPath.length > 0 ? detailTask : selectedTask;
|
|
23232
|
+
if (state.mode === "move" && overlayTarget) {
|
|
23233
|
+
return /* @__PURE__ */ import_react43.default.createElement(
|
|
23584
23234
|
MoveOverlay,
|
|
23585
23235
|
{
|
|
23586
23236
|
columns,
|
|
23587
23237
|
selectedIndex: state.moveTargetIndex,
|
|
23588
|
-
taskId:
|
|
23589
|
-
taskTitle:
|
|
23238
|
+
taskId: overlayTarget.id,
|
|
23239
|
+
taskTitle: overlayTarget.title,
|
|
23590
23240
|
width: termWidth
|
|
23591
23241
|
}
|
|
23592
23242
|
);
|
|
23593
23243
|
}
|
|
23594
|
-
if (state.mode === "delete-confirm" &&
|
|
23595
|
-
return /* @__PURE__ */
|
|
23244
|
+
if (state.mode === "delete-confirm" && overlayTarget) {
|
|
23245
|
+
return /* @__PURE__ */ import_react43.default.createElement(
|
|
23596
23246
|
DeleteConfirmOverlay,
|
|
23597
23247
|
{
|
|
23598
|
-
taskId:
|
|
23599
|
-
taskTitle:
|
|
23248
|
+
taskId: overlayTarget.id,
|
|
23249
|
+
taskTitle: overlayTarget.title,
|
|
23600
23250
|
width: termWidth
|
|
23601
23251
|
}
|
|
23602
23252
|
);
|
|
23603
23253
|
}
|
|
23604
23254
|
if (state.mode === "complete-confirm" && state.completeConfirm) {
|
|
23605
|
-
return /* @__PURE__ */
|
|
23255
|
+
return /* @__PURE__ */ import_react43.default.createElement(CompleteConfirmOverlay, { target: state.completeConfirm, width: termWidth });
|
|
23606
23256
|
}
|
|
23607
23257
|
if (state.mode === "add") {
|
|
23608
|
-
return /* @__PURE__ */
|
|
23258
|
+
return /* @__PURE__ */ import_react43.default.createElement(AddOverlay, { title: state.newTaskTitle, columnName, thenEdit });
|
|
23609
23259
|
}
|
|
23610
|
-
const list = /* @__PURE__ */
|
|
23260
|
+
const list = /* @__PURE__ */ import_react43.default.createElement(
|
|
23611
23261
|
DocumentList,
|
|
23612
23262
|
{
|
|
23613
23263
|
rows,
|
|
23614
23264
|
selectedIndex,
|
|
23615
23265
|
viewportHeight,
|
|
23616
23266
|
width: listWidth,
|
|
23617
|
-
|
|
23267
|
+
archived,
|
|
23268
|
+
emptyMessage: hasFilter ? EMPTY.noMatches : archived ? EMPTY.nothingCompleted : typeFilter ? EMPTY.noType(typeFilter) : EMPTY.emptyColumn
|
|
23618
23269
|
}
|
|
23619
23270
|
);
|
|
23620
|
-
if (!detailOpen || !
|
|
23621
|
-
return
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
23625
|
-
|
|
23626
|
-
|
|
23627
|
-
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
23631
|
-
|
|
23271
|
+
if (!detailOpen || !detailTask) return list;
|
|
23272
|
+
return (
|
|
23273
|
+
// flexShrink={0} down this whole chain: detail v2 can render more lines
|
|
23274
|
+
// than the nominal viewport (a doc with children + subtasks + a full
|
|
23275
|
+
// contract + activity easily does, in a modest terminal). Letting yoga
|
|
23276
|
+
// shrink to fit corrupts the layout — rows silently lose height and their
|
|
23277
|
+
// text vanishes — rather than the frame simply growing taller, which ink
|
|
23278
|
+
// and real terminals handle fine.
|
|
23279
|
+
/* @__PURE__ */ import_react43.default.createElement(Box_default, { flexDirection: "row", flexGrow: 1, flexShrink: 0 }, /* @__PURE__ */ import_react43.default.createElement(Box_default, { width: listWidth, flexDirection: "column", flexShrink: 0 }, list), /* @__PURE__ */ import_react43.default.createElement(Box_default, { width: detailPaneWidth, flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ import_react43.default.createElement(
|
|
23280
|
+
DetailView,
|
|
23281
|
+
{
|
|
23282
|
+
task: detailTask,
|
|
23283
|
+
columnLabel: detailColumnLabel,
|
|
23284
|
+
width: detailPaneWidth,
|
|
23285
|
+
height: viewportHeight,
|
|
23286
|
+
breadcrumb: state.detailPath,
|
|
23287
|
+
parent: detailParent,
|
|
23288
|
+
children: detailChildren,
|
|
23289
|
+
activity: detailActivity,
|
|
23290
|
+
cursor: state.detailCursor,
|
|
23291
|
+
scrollOffset: state.detailScroll
|
|
23292
|
+
}
|
|
23293
|
+
)))
|
|
23294
|
+
);
|
|
23632
23295
|
}
|
|
23633
23296
|
|
|
23634
23297
|
// src/commands/tui.tsx
|
|
23635
23298
|
async function tuiCommand(options) {
|
|
23636
23299
|
const filePath = resolveCliBrainfilePath(options.file);
|
|
23637
|
-
if (!
|
|
23638
|
-
console.error(`
|
|
23639
|
-
console.
|
|
23640
|
-
console.log("To create a new brainfile, run:");
|
|
23641
|
-
console.log(" brainfile init");
|
|
23300
|
+
if (!fs6.existsSync(filePath)) {
|
|
23301
|
+
console.error(`File not found: ${filePath}`);
|
|
23302
|
+
console.error("Create a board with: brainfile init");
|
|
23642
23303
|
process.exit(1);
|
|
23643
23304
|
}
|
|
23644
23305
|
if (!process.stdin.isTTY || !process.stdin.setRawMode) {
|
|
23645
|
-
console.error("
|
|
23646
|
-
console.
|
|
23647
|
-
console.log("The TUI cannot run in non-interactive environments.");
|
|
23648
|
-
console.log("Please run this command in a standard terminal (not piped or in a non-TTY context).");
|
|
23306
|
+
console.error("The board needs an interactive terminal.");
|
|
23307
|
+
console.error("Run this in a real terminal, not a pipe.");
|
|
23649
23308
|
process.exit(1);
|
|
23650
23309
|
}
|
|
23651
23310
|
if (shouldSuggestV2Migration(filePath)) {
|
|
@@ -23655,7 +23314,7 @@ async function tuiCommand(options) {
|
|
|
23655
23314
|
console.log("");
|
|
23656
23315
|
markV2MigrationHintShown(filePath);
|
|
23657
23316
|
}
|
|
23658
|
-
const { waitUntilExit } = render_default(/* @__PURE__ */
|
|
23317
|
+
const { waitUntilExit } = render_default(/* @__PURE__ */ import_react44.default.createElement(BrainfileTUI, { filePath }));
|
|
23659
23318
|
await waitUntilExit();
|
|
23660
23319
|
process.stdout.write("\x1B[2J");
|
|
23661
23320
|
process.stdout.write("\x1B[H");
|