@plainconceptsplatform/loop-task 2.6.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/LICENSE +21 -0
- package/README.md +534 -0
- package/dist/app/App.js +132 -0
- package/dist/app/BoardLayout.js +5 -0
- package/dist/app/index.js +48 -0
- package/dist/app/providers/index.js +1 -0
- package/dist/app/router/index.js +16 -0
- package/dist/app/types.js +1 -0
- package/dist/cli/import-validator.js +152 -0
- package/dist/cli/import-writer.js +81 -0
- package/dist/cli.js +573 -0
- package/dist/client/cli-format.js +7 -0
- package/dist/client/commands.js +162 -0
- package/dist/client/ipc.js +96 -0
- package/dist/client/project-commands.js +164 -0
- package/dist/core/command/command-runner.js +300 -0
- package/dist/core/command/process-tree.js +73 -0
- package/dist/core/command/resolve-cwd.js +8 -0
- package/dist/core/command/stdout-capture-transform.js +73 -0
- package/dist/core/context/context-parser.js +65 -0
- package/dist/core/context/log-context.js +3 -0
- package/dist/core/context/template.js +20 -0
- package/dist/core/context/validate-context.js +19 -0
- package/dist/core/foreground/index.js +81 -0
- package/dist/core/logging/bounded-log-reader.js +368 -0
- package/dist/core/logging/log-follower.js +26 -0
- package/dist/core/logging/log-parser.js +29 -0
- package/dist/core/logging/log-rotator.js +34 -0
- package/dist/core/logging/rotating-log-stream.js +137 -0
- package/dist/core/loop/chain-executor.js +171 -0
- package/dist/core/loop/delay-utils.js +65 -0
- package/dist/core/loop/loop-controller.js +240 -0
- package/dist/core/loop/loop-runner.js +194 -0
- package/dist/core/loop/run-executor.js +144 -0
- package/dist/core/loop/types.js +1 -0
- package/dist/core/scheduling/index.js +16 -0
- package/dist/daemon/daemon-log.js +11 -0
- package/dist/daemon/diagnostics.js +85 -0
- package/dist/daemon/http/helpers.js +81 -0
- package/dist/daemon/http/openapi.js +119 -0
- package/dist/daemon/http/route-loops.js +311 -0
- package/dist/daemon/http/route-misc.js +60 -0
- package/dist/daemon/http/route-projects.js +55 -0
- package/dist/daemon/http/route-tasks.js +141 -0
- package/dist/daemon/http/routes.js +19 -0
- package/dist/daemon/http/server.js +82 -0
- package/dist/daemon/http/sse.js +36 -0
- package/dist/daemon/index.js +189 -0
- package/dist/daemon/ipc/send.js +5 -0
- package/dist/daemon/managers/loop-entry.js +27 -0
- package/dist/daemon/managers/loop-manager.js +346 -0
- package/dist/daemon/managers/loop-options.js +17 -0
- package/dist/daemon/managers/loop-serialization.js +53 -0
- package/dist/daemon/managers/project-manager.js +170 -0
- package/dist/daemon/managers/task-manager.js +94 -0
- package/dist/daemon/mcp/index.js +2 -0
- package/dist/daemon/mcp/openapi-sync.js +50 -0
- package/dist/daemon/mcp/server.js +167 -0
- package/dist/daemon/mcp/tools.js +382 -0
- package/dist/daemon/recipe/deferred-reload.js +32 -0
- package/dist/daemon/recipe/diagram-reader.js +26 -0
- package/dist/daemon/recipe/file-writer.js +52 -0
- package/dist/daemon/recipe/id-remapper.js +46 -0
- package/dist/daemon/recipe/runtime-state.js +23 -0
- package/dist/daemon/recipe/scanner.js +165 -0
- package/dist/daemon/recipe/task-store.js +33 -0
- package/dist/daemon/recipe/validator.js +35 -0
- package/dist/daemon/server/handlers/diagnostics-handlers.js +7 -0
- package/dist/daemon/server/handlers/index.js +101 -0
- package/dist/daemon/server/handlers/log-handlers.js +129 -0
- package/dist/daemon/server/handlers/loop-handlers.js +64 -0
- package/dist/daemon/server/handlers/project-handlers.js +42 -0
- package/dist/daemon/server/handlers/settings-handlers.js +8 -0
- package/dist/daemon/server/handlers/task-handlers.js +20 -0
- package/dist/daemon/server/handlers/telemetry-handlers.js +5 -0
- package/dist/daemon/server/index.js +117 -0
- package/dist/daemon/settings-manager.js +73 -0
- package/dist/daemon/spawner/index.js +204 -0
- package/dist/daemon/state/index.js +209 -0
- package/dist/daemon/telemetry/agent-integrations/agent-integration.js +1 -0
- package/dist/daemon/telemetry/agent-integrations/claude-code-integration.js +55 -0
- package/dist/daemon/telemetry/agent-integrations/detect-agent-integration.js +20 -0
- package/dist/daemon/telemetry/agent-integrations/index.js +3 -0
- package/dist/daemon/telemetry/agent-integrations/opencode-integration.js +72 -0
- package/dist/daemon/telemetry/index.js +6 -0
- package/dist/daemon/telemetry/noop-telemetry-adapter.js +53 -0
- package/dist/daemon/telemetry/open-telemetry-adapter.js +464 -0
- package/dist/daemon/telemetry/telemetry-manager.js +147 -0
- package/dist/daemon/telemetry/telemetry-redaction.js +67 -0
- package/dist/daemon/telemetry/telemetry-types.js +45 -0
- package/dist/daemon/telemetry/telemetry.js +1 -0
- package/dist/daemon/watcher/index.js +250 -0
- package/dist/duration.js +24 -0
- package/dist/entities/loops/filters.js +109 -0
- package/dist/entities/loops/index.js +1 -0
- package/dist/entities/projects/filters.js +69 -0
- package/dist/entities/projects/index.js +1 -0
- package/dist/entities/tasks/filters.js +41 -0
- package/dist/entities/tasks/index.js +1 -0
- package/dist/entry.js +16 -0
- package/dist/esm-loader.js +39 -0
- package/dist/features/chain-editor/ChainEditor.js +116 -0
- package/dist/features/chain-editor/mermaidToAscii.js +375 -0
- package/dist/features/chain-editor/renderChainDiagram.js +106 -0
- package/dist/features/code-editor/CodeEditorModal.js +84 -0
- package/dist/features/code-editor/CodeEditorPreview.js +14 -0
- package/dist/features/code-editor/useEditorKeyboard.js +150 -0
- package/dist/features/code-editor/useModalDimensions.js +9 -0
- package/dist/features/commands/commands.js +132 -0
- package/dist/features/commands/useCommandHandlers.js +280 -0
- package/dist/features/commands/useContextualActions.js +78 -0
- package/dist/features/commands/useGlobalShortcuts.js +154 -0
- package/dist/features/forms/FormRouter.js +46 -0
- package/dist/features/overlays/ContextHelpModal.js +8 -0
- package/dist/features/overlays/DiagramModal.js +45 -0
- package/dist/features/overlays/ExportModal.js +51 -0
- package/dist/features/overlays/HelpGuideModal.js +23 -0
- package/dist/features/overlays/HelpModal.js +59 -0
- package/dist/features/overlays/OverlayStack.js +10 -0
- package/dist/features/overlays/ProjectsModal.js +42 -0
- package/dist/features/overlays/TaskPickerModal.js +62 -0
- package/dist/features/overlays/WelcomeScreen.js +74 -0
- package/dist/features/overlays/useOverlayStack.js +55 -0
- package/dist/features/state/useAppState.js +151 -0
- package/dist/logger.js +25 -0
- package/dist/loop-config.js +152 -0
- package/dist/shared/clipboard.js +111 -0
- package/dist/shared/config/constants.js +104 -0
- package/dist/shared/config/paths.js +67 -0
- package/dist/shared/container/index.js +24 -0
- package/dist/shared/fs-utils.js +44 -0
- package/dist/shared/hooks/useBreakpoint.js +11 -0
- package/dist/shared/hooks/useDaemonSettings.js +41 -0
- package/dist/shared/hooks/useInject.js +6 -0
- package/dist/shared/hooks/useLogStream.js +37 -0
- package/dist/shared/hooks/useLoopFormValidation.js +131 -0
- package/dist/shared/hooks/useLoopPolling.js +30 -0
- package/dist/shared/hooks/useMouseScroll.js +56 -0
- package/dist/shared/hooks/useUndoRedo.js +77 -0
- package/dist/shared/i18n/en.json +709 -0
- package/dist/shared/i18n/index.js +11 -0
- package/dist/shared/providers/InversifyProvider.js +6 -0
- package/dist/shared/services/export-service.js +43 -0
- package/dist/shared/services/log-service.js +28 -0
- package/dist/shared/services/loop-service.js +78 -0
- package/dist/shared/services/project-service.js +39 -0
- package/dist/shared/services/settings-service.js +102 -0
- package/dist/shared/services/task-service.js +41 -0
- package/dist/shared/services/types.js +8 -0
- package/dist/shared/sleep.js +17 -0
- package/dist/shared/tail.js +4 -0
- package/dist/shared/ui/Button.js +18 -0
- package/dist/shared/ui/DebugPanel.js +9 -0
- package/dist/shared/ui/FocusableButton.js +18 -0
- package/dist/shared/ui/FocusableInput.js +72 -0
- package/dist/shared/ui/FocusableList.js +48 -0
- package/dist/shared/ui/Modal.js +13 -0
- package/dist/shared/ui/SelectModal.js +78 -0
- package/dist/shared/ui/Toast.js +37 -0
- package/dist/shared/ui/format.js +117 -0
- package/dist/shared/ui/hooks/useHoverState.js +11 -0
- package/dist/shared/ui/state.js +9 -0
- package/dist/shared/ui/theme.js +96 -0
- package/dist/shared/utils/log-lines.js +13 -0
- package/dist/shared/utils/paste.js +37 -0
- package/dist/shared/utils/syntax.js +128 -0
- package/dist/shared/utils/validation.js +56 -0
- package/dist/types.js +1 -0
- package/dist/visual-evidence/capture.js +51 -0
- package/dist/visual-evidence/cli.js +85 -0
- package/dist/visual-evidence/evidence-required.js +71 -0
- package/dist/visual-evidence/index.js +10 -0
- package/dist/visual-evidence/launch.js +61 -0
- package/dist/visual-evidence/manifest.js +32 -0
- package/dist/visual-evidence/openspec-resolver.js +45 -0
- package/dist/visual-evidence/publish.js +126 -0
- package/dist/visual-evidence/run.js +121 -0
- package/dist/visual-evidence/scenario-registry.js +132 -0
- package/dist/widgets/command-input/CommandDropdown.js +69 -0
- package/dist/widgets/command-input/CommandInput.js +133 -0
- package/dist/widgets/command-input/HintBar.js +9 -0
- package/dist/widgets/command-input/InputModes.js +72 -0
- package/dist/widgets/commands-browser/CommandsBrowserModal.js +85 -0
- package/dist/widgets/header/Header.js +54 -0
- package/dist/widgets/header/TabBar.js +20 -0
- package/dist/widgets/left-panel/LeftPanel.js +34 -0
- package/dist/widgets/left-panel/Navigator.js +107 -0
- package/dist/widgets/left-panel/ProjectsPage.js +76 -0
- package/dist/widgets/left-panel/ProjectsPageParts.js +34 -0
- package/dist/widgets/left-panel/TaskBrowser.js +132 -0
- package/dist/widgets/log-modal/LogModal.js +211 -0
- package/dist/widgets/loop-form/CreateForm.js +82 -0
- package/dist/widgets/loop-form/TextField.js +9 -0
- package/dist/widgets/loop-form/WizardForm.js +199 -0
- package/dist/widgets/loop-form/useCreateSteps.js +135 -0
- package/dist/widgets/loop-form/useHandleComplete.js +94 -0
- package/dist/widgets/project-form/ProjectForm.js +83 -0
- package/dist/widgets/right-panel/Inspector.js +41 -0
- package/dist/widgets/right-panel/RightPanel.js +38 -0
- package/dist/widgets/right-panel/RunHistory.js +187 -0
- package/dist/widgets/task-form/TaskForm.js +272 -0
- package/package.json +119 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
|
+
import { t } from "../../shared/i18n/index.js";
|
|
6
|
+
import { EXPORT_MAX_PREVIEW_LINES } from "../../shared/config/constants.js";
|
|
7
|
+
import { copyToClipboard } from "../../shared/clipboard.js";
|
|
8
|
+
import { useMouseScroll } from "../../shared/hooks/useMouseScroll.js";
|
|
9
|
+
const VISIBLE_LINES = 20;
|
|
10
|
+
export function ExportModal(props) {
|
|
11
|
+
const lines = props.json.split("\n");
|
|
12
|
+
const totalLines = lines.length;
|
|
13
|
+
const truncated = totalLines > EXPORT_MAX_PREVIEW_LINES;
|
|
14
|
+
const displayLines = truncated ? lines.slice(0, EXPORT_MAX_PREVIEW_LINES) : lines;
|
|
15
|
+
const maxScroll = Math.max(0, displayLines.length - VISIBLE_LINES);
|
|
16
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
17
|
+
useInput((input, key) => {
|
|
18
|
+
// Bracketed paste: content wrapped in ESC[200~ ... ESC[201~. Must come
|
|
19
|
+
// before the escape check the leading ESC trips key.escape and would
|
|
20
|
+
// close the modal on a right-click paste.
|
|
21
|
+
if (input.includes("\x1b[200~")) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (input.includes("[<")) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (key.escape) {
|
|
28
|
+
props.onClose();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (key.downArrow) {
|
|
32
|
+
setScrollOffset((o) => Math.min(o + 1, maxScroll));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (key.upArrow) {
|
|
36
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (input === "c" && !key.ctrl) {
|
|
40
|
+
copyToClipboard(props.json);
|
|
41
|
+
props.onCopy?.();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
useMouseScroll({
|
|
46
|
+
onScrollUp: () => setScrollOffset((o) => Math.max(0, o - 1)),
|
|
47
|
+
onScrollDown: () => setScrollOffset((o) => Math.min(o + 1, maxScroll)),
|
|
48
|
+
});
|
|
49
|
+
const visible = displayLines.slice(scrollOffset, scrollOffset + VISIBLE_LINES);
|
|
50
|
+
return (_jsxs(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: theme.accent.brand, paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.accent.brand, bold: true, children: t("export.modalTitle") }) }), props.error ? (_jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: theme.semantic.danger, children: t("export.error", { message: props.error }) }) })) : (_jsxs(_Fragment, { children: [props.filePath ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.accent.brand, children: t("export.filePath", { path: props.filePath }) }) })) : null, _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [visible.map((line, i) => (_jsx(Text, { color: theme.text.primary, wrap: "truncate", children: line }, scrollOffset + i))), truncated ? (_jsx(Text, { color: theme.text.muted, children: t("export.truncated", { total: String(totalLines) }) })) : null] })] })), _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsx(Text, { color: theme.text.muted, children: t("export.hint") }), _jsxs(Text, { color: theme.text.muted, children: ["[", scrollOffset, "-", scrollOffset + visible.length, "/", totalLines, "]"] })] })] }));
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
4
|
+
import { Modal } from "../../shared/ui/Modal.js";
|
|
5
|
+
import { t } from "../../shared/i18n/index.js";
|
|
6
|
+
export function HelpGuideModal(props) {
|
|
7
|
+
useInput((_input, key) => {
|
|
8
|
+
if (_input.includes("[<"))
|
|
9
|
+
return;
|
|
10
|
+
if (key.escape || _input === "q") {
|
|
11
|
+
props.onClose();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
const sections = [
|
|
15
|
+
{ title: "", body: t("helpGuide.intro") },
|
|
16
|
+
{ title: t("helpGuide.commands"), body: t("helpGuide.examples") },
|
|
17
|
+
{ title: t("helpGuide.allCommands"), body: t("helpGuide.allCommands") },
|
|
18
|
+
{ title: t("helpGuide.confirm"), body: t("helpGuide.confirm") },
|
|
19
|
+
{ title: t("helpGuide.navigation"), body: t("helpGuide.navigation") },
|
|
20
|
+
{ title: t("helpGuide.ctrl"), body: t("helpGuide.ctrl") },
|
|
21
|
+
];
|
|
22
|
+
return (_jsxs(Modal, { title: t("helpGuide.title"), onClose: props.onClose, width: "70%", children: [sections.map((section, i) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [section.title.length > 0 ? (_jsx(Text, { color: theme.accent.brand, bold: true, children: section.title })) : null, _jsx(Text, { color: theme.text.primary, children: section.body })] }, i))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.text.muted, children: t("helpGuide.esc") }) })] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
4
|
+
import { Modal } from "../../shared/ui/Modal.js";
|
|
5
|
+
import { t } from "../../shared/i18n/index.js";
|
|
6
|
+
function helpEntries(view) {
|
|
7
|
+
switch (view) {
|
|
8
|
+
case "board":
|
|
9
|
+
return [
|
|
10
|
+
{ key: t("board.helpKeyMove"), desc: t("board.helpMoveSelection") },
|
|
11
|
+
{ key: t("board.helpKeyEnter"), desc: t("board.helpToggleDetail") },
|
|
12
|
+
{ key: t("board.helpKeyN"), desc: t("board.helpCreate") },
|
|
13
|
+
{ key: t("board.helpKeyE"), desc: t("board.helpEdit") },
|
|
14
|
+
{ key: t("board.helpKeyP"), desc: t("board.helpPauseResume") },
|
|
15
|
+
{ key: t("board.helpKeyX"), desc: t("board.helpForceRun") },
|
|
16
|
+
{ key: t("board.helpKeyD"), desc: t("board.helpDelete") },
|
|
17
|
+
{ key: t("board.helpKeySlash"), desc: t("board.helpSearch") },
|
|
18
|
+
{ key: t("board.helpKeyF"), desc: t("board.helpCycleFilter") },
|
|
19
|
+
{ key: t("board.helpKeyS"), desc: t("board.helpCycleSort") },
|
|
20
|
+
{ key: t("board.helpKeyLeftRight"), desc: t("board.helpSwitchPanel") },
|
|
21
|
+
{ key: t("board.helpKeyH"), desc: t("board.helpToggleHelp") },
|
|
22
|
+
{ key: t("board.helpKeyEsc"), desc: t("board.helpBack") },
|
|
23
|
+
{ key: t("board.helpKeyQ"), desc: t("board.helpQuit") },
|
|
24
|
+
];
|
|
25
|
+
case "tasks":
|
|
26
|
+
return [
|
|
27
|
+
{ key: t("board.helpKeyMove"), desc: t("board.helpMoveSelection") },
|
|
28
|
+
{ key: t("board.helpKeyEnter"), desc: t("board.taskActionSelect") },
|
|
29
|
+
{ key: t("board.helpKeyN"), desc: t("board.helpCreateTask") },
|
|
30
|
+
{ key: t("board.helpKeyE"), desc: t("board.taskActionEdit") },
|
|
31
|
+
{ key: t("board.helpKeyD"), desc: t("board.actionDelete") },
|
|
32
|
+
{ key: t("board.helpKeyH"), desc: t("board.helpToggleHelp") },
|
|
33
|
+
{ key: t("board.helpKeyEsc"), desc: t("board.helpBack") },
|
|
34
|
+
{ key: t("board.helpKeyQ"), desc: t("board.helpQuit") },
|
|
35
|
+
];
|
|
36
|
+
case "projects":
|
|
37
|
+
return [
|
|
38
|
+
{ key: t("board.helpKeyMove"), desc: t("board.helpMoveSelection") },
|
|
39
|
+
{ key: t("board.helpKeyEnter"), desc: t("project.editProjectLabel") },
|
|
40
|
+
{ key: t("board.helpKeyN"), desc: t("project.keyNewHint") },
|
|
41
|
+
{ key: t("board.helpKeyE"), desc: t("project.keyEditHint") },
|
|
42
|
+
{ key: t("board.helpKeyD"), desc: t("project.keyDeleteHint") },
|
|
43
|
+
{ key: t("board.helpKeyH"), desc: t("board.helpToggleHelp") },
|
|
44
|
+
{ key: t("board.helpKeyEsc"), desc: t("board.helpBack") },
|
|
45
|
+
{ key: t("board.helpKeyQ"), desc: t("board.helpQuit") },
|
|
46
|
+
];
|
|
47
|
+
default:
|
|
48
|
+
return [
|
|
49
|
+
{ key: t("board.hintKeyTab"), desc: t("board.hintNextField") },
|
|
50
|
+
{ key: t("board.helpKeyEnter"), desc: t("board.hintApply") },
|
|
51
|
+
{ key: t("board.helpKeyEsc"), desc: t("board.hintCancel") },
|
|
52
|
+
{ key: t("board.helpKeyQ"), desc: t("board.helpQuit") },
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function HelpModal(props) {
|
|
57
|
+
const entries = helpEntries(props.view);
|
|
58
|
+
return (_jsx(Modal, { title: "Shortcuts", onClose: props.onClose, width: "60%", children: entries.map((entry, i) => (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent.brand, children: entry.key.padEnd(16) }), _jsx(Text, { color: theme.text.primary, children: entry.desc })] }, i))) }));
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LogModal } from "../../widgets/log-modal/LogModal.js";
|
|
3
|
+
import { CommandsBrowserModal } from "../../widgets/commands-browser/CommandsBrowserModal.js";
|
|
4
|
+
import { ContextHelpModal } from "./ContextHelpModal.js";
|
|
5
|
+
import { ExportModal } from "./ExportModal.js";
|
|
6
|
+
import { DiagramModal } from "./DiagramModal.js";
|
|
7
|
+
import { ToastStack } from "../../shared/ui/Toast.js";
|
|
8
|
+
export function OverlayStack(props) {
|
|
9
|
+
return (_jsxs(_Fragment, { children: [props.commandsBrowserOpen ? (_jsx(CommandsBrowserModal, { context: props.commandContext, onClose: props.onCommandsBrowserClose, onExecute: props.onCommandsBrowserExecute })) : null, props.contextHelpOpen ? _jsx(ContextHelpModal, { onClose: props.onContextHelpClose }) : null, props.exportModal ? (_jsx(ExportModal, { json: props.exportModal.json, filePath: props.exportModal.filePath, error: props.exportModal.error, onClose: props.onExportModalClose, onCopy: props.onExportCopy })) : null, props.diagramModal ? (_jsx(DiagramModal, { diagramText: props.diagramModal, onClose: props.onDiagramModalClose })) : null, props.logModalRun ? (_jsx(LogModal, { loopId: props.logModalLoopId, run: props.logModalRun, logLines: props.logModalLines, loading: props.logModalLoading, onClose: props.onLogModalClose, onCopy: props.onLogCopy })) : null, _jsx(ToastStack, { toasts: props.toasts })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
|
+
import { t } from "../../shared/i18n/index.js";
|
|
6
|
+
export function ProjectsModal(props) {
|
|
7
|
+
const { projects, loops, currentProjectId, onSelect, onClose } = props;
|
|
8
|
+
const [selectedIndex, setSelectedIndex] = React.useState(() => {
|
|
9
|
+
const allIdx = projects.findIndex((p) => p.id === "all");
|
|
10
|
+
if (allIdx >= 0)
|
|
11
|
+
return allIdx;
|
|
12
|
+
const currentIdx = projects.findIndex((p) => p.id === currentProjectId);
|
|
13
|
+
return currentIdx >= 0 ? currentIdx : 0;
|
|
14
|
+
});
|
|
15
|
+
useInput((input, key) => {
|
|
16
|
+
if (input.includes("[<"))
|
|
17
|
+
return;
|
|
18
|
+
if (key.upArrow) {
|
|
19
|
+
setSelectedIndex((i) => i <= 0 ? projects.length - 1 : i - 1);
|
|
20
|
+
}
|
|
21
|
+
else if (key.downArrow) {
|
|
22
|
+
setSelectedIndex((i) => i >= projects.length - 1 ? 0 : i + 1);
|
|
23
|
+
}
|
|
24
|
+
else if (key.return) {
|
|
25
|
+
const project = projects[selectedIndex];
|
|
26
|
+
if (project)
|
|
27
|
+
onSelect(project.id);
|
|
28
|
+
}
|
|
29
|
+
else if (key.escape) {
|
|
30
|
+
onClose();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const loopCount = (pid) => loops.filter((l) => (l.projectId ?? "default") === pid).length;
|
|
34
|
+
return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", children: _jsxs(Box, { borderStyle: "round", borderColor: theme.accent.brand, backgroundColor: theme.bg.elevated, flexDirection: "column", paddingX: 2, paddingY: 1, minWidth: 40, maxWidth: 60, children: [_jsx(Text, { color: theme.accent.brand, bold: true, children: t("project.filterTitle") }), projects.map((project, i) => {
|
|
35
|
+
const isSelected = i === selectedIndex;
|
|
36
|
+
const isActive = project.id === currentProjectId;
|
|
37
|
+
const prefix = isSelected ? "\u203a " : " ";
|
|
38
|
+
const bg = isSelected ? theme.bg.active : undefined;
|
|
39
|
+
const fg = isSelected ? theme.text.inverse : isActive ? theme.accent.brand : theme.text.primary;
|
|
40
|
+
return (_jsx(Box, { backgroundColor: bg, children: _jsxs(Text, { color: fg, children: [prefix, _jsx(Text, { color: project.color ?? theme.text.primary, children: "\u25cf " }), project.name, " ", _jsxs(Text, { color: theme.text.muted, children: ["(", loopCount(project.id), ")"] })] }) }, project.id));
|
|
41
|
+
}), _jsxs(Text, { color: theme.text.muted, children: ["\n", t("board.logModalEscClose")] })] }) }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo, useCallback } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
|
+
import { t } from "../../shared/i18n/index.js";
|
|
6
|
+
const MAX_VISIBLE = 10;
|
|
7
|
+
export function TaskPickerModal(props) {
|
|
8
|
+
const [query, setQuery] = useState("");
|
|
9
|
+
const [cursor, setCursor] = useState(0);
|
|
10
|
+
const filtered = useMemo(() => {
|
|
11
|
+
const q = query.trim().toLowerCase();
|
|
12
|
+
if (!q)
|
|
13
|
+
return props.tasks;
|
|
14
|
+
return props.tasks.filter((task) => task.name.toLowerCase().includes(q) ||
|
|
15
|
+
task.command.toLowerCase().includes(q));
|
|
16
|
+
}, [props.tasks, query]);
|
|
17
|
+
const clampedCursor = filtered.length > 0 ? cursor % filtered.length : 0;
|
|
18
|
+
const selectAt = useCallback((idx) => {
|
|
19
|
+
const task = filtered[idx];
|
|
20
|
+
if (task)
|
|
21
|
+
props.onSelect(task);
|
|
22
|
+
}, [filtered, props]);
|
|
23
|
+
useInput((input, key) => {
|
|
24
|
+
if (input.includes("[<"))
|
|
25
|
+
return;
|
|
26
|
+
if (key.escape) {
|
|
27
|
+
props.onClose();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (key.return) {
|
|
31
|
+
selectAt(clampedCursor);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (key.upArrow) {
|
|
35
|
+
setCursor((prev) => prev <= 0 ? Math.max(0, filtered.length - 1) : prev - 1);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (key.downArrow) {
|
|
39
|
+
setCursor((prev) => prev >= filtered.length - 1 ? 0 : prev + 1);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (key.backspace || key.delete) {
|
|
43
|
+
setQuery((prev) => prev.slice(0, -1));
|
|
44
|
+
setCursor(0);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (key.ctrl)
|
|
48
|
+
return;
|
|
49
|
+
if (input.length === 1 && input >= " " && input <= "~") {
|
|
50
|
+
setQuery((prev) => prev + input);
|
|
51
|
+
setCursor(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const start = Math.max(0, clampedCursor - Math.floor(MAX_VISIBLE / 2));
|
|
55
|
+
const end = Math.min(filtered.length, start + MAX_VISIBLE);
|
|
56
|
+
const visible = filtered.slice(start, end);
|
|
57
|
+
return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { width: 60, height: Math.min(20, visible.length + 8), flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: theme.accent.task, paddingX: 1, paddingY: 0, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: theme.accent.task, bold: true, children: t("taskPicker.title") }), _jsx(Text, { color: theme.text.muted, children: "esc" })] }), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: theme.text.muted, children: "\u203a " }), _jsx(Text, { color: query ? theme.text.primary : theme.text.muted, children: query || t("taskPicker.searchPlaceholder") }), _jsx(Text, { inverse: true, children: " " })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: t("taskPicker.empty") })) : (visible.map((task, visIdx) => {
|
|
58
|
+
const realIdx = start + visIdx;
|
|
59
|
+
const isSelected = realIdx === clampedCursor;
|
|
60
|
+
return (_jsxs(Box, { backgroundColor: isSelected ? theme.bg.active : undefined, children: [_jsx(Text, { color: isSelected ? theme.text.inverse : theme.text.muted, children: isSelected ? "\u276f " : " " }), _jsx(Text, { color: isSelected ? theme.text.inverse : theme.text.primary, children: task.name }), _jsx(Text, { color: isSelected ? theme.text.inverse : theme.text.muted, children: " " + task.command })] }, task.id));
|
|
61
|
+
})) }), _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.text.muted, children: [filtered.length, "/", props.tasks.length] }), _jsx(Text, { color: theme.text.muted, children: t("taskPicker.hint") })] })] }) }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { darkTheme as theme } from "../../shared/ui/theme.js";
|
|
5
|
+
import { useInject } from "../../shared/hooks/useInject.js";
|
|
6
|
+
import { TYPES } from "../../shared/services/types.js";
|
|
7
|
+
import { parseDuration } from "../../duration.js";
|
|
8
|
+
const EXAMPLES = [
|
|
9
|
+
{ label: "Run tests every 30m", interval: "30m", command: "npm", args: ["test"] },
|
|
10
|
+
{ label: "Watch for GitHub issues every 5m", interval: "5m", command: "gh", args: ["issue", "list"] },
|
|
11
|
+
{ label: "Sync deps every hour", interval: "1h", command: "npm", args: ["outdated"] },
|
|
12
|
+
];
|
|
13
|
+
export function WelcomeScreen(props) {
|
|
14
|
+
const loopService = useInject(TYPES.LoopService);
|
|
15
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
16
|
+
const [creating, setCreating] = React.useState(null);
|
|
17
|
+
const items = [...EXAMPLES.map((e) => e.label), "Create empty loop"];
|
|
18
|
+
useInput((_input, key) => {
|
|
19
|
+
if (_input.includes("[<"))
|
|
20
|
+
return;
|
|
21
|
+
if (creating)
|
|
22
|
+
return;
|
|
23
|
+
if (key.upArrow) {
|
|
24
|
+
setSelectedIndex((i) => i <= 0 ? items.length - 1 : i - 1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (key.downArrow) {
|
|
28
|
+
setSelectedIndex((i) => i >= items.length - 1 ? 0 : i + 1);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (key.return) {
|
|
32
|
+
if (selectedIndex === EXAMPLES.length) {
|
|
33
|
+
props.onCreateEmpty();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const example = EXAMPLES[selectedIndex];
|
|
37
|
+
if (example) {
|
|
38
|
+
setCreating(example.label);
|
|
39
|
+
const options = {
|
|
40
|
+
interval: parseDuration(example.interval),
|
|
41
|
+
taskId: null,
|
|
42
|
+
command: example.command,
|
|
43
|
+
commandArgs: example.args,
|
|
44
|
+
cwd: "",
|
|
45
|
+
immediate: false,
|
|
46
|
+
maxRuns: null,
|
|
47
|
+
verbose: false,
|
|
48
|
+
description: example.label,
|
|
49
|
+
projectId: "default",
|
|
50
|
+
offset: null,
|
|
51
|
+
};
|
|
52
|
+
loopService.create(options, example.interval)
|
|
53
|
+
.then(() => props.onRefresh())
|
|
54
|
+
.then(() => props.onCreateLoop())
|
|
55
|
+
.catch(() => setCreating(null));
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (_input === "n") {
|
|
60
|
+
props.onCreateEmpty();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (_input === "h") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: _jsxs(Box, { borderStyle: "round", borderColor: theme.accent.brand, backgroundColor: theme.bg.surface, paddingX: 4, paddingY: 1, flexDirection: "column", children: [_jsxs(Text, { color: theme.accent.brand, bold: true, children: ["\n", "Welcome to loop-task!", "\n"] }), _jsx(Text, { color: theme.text.secondary, children: "Loop engineering: define a purpose," }), _jsxs(Text, { color: theme.text.secondary, children: ["give it an interval, let it iterate.", "\n"] }), _jsxs(Text, { color: theme.text.muted, children: ["No loops yet. Here are some examples:", "\n"] }), items.map((label, i) => {
|
|
68
|
+
const isSelected = i === selectedIndex;
|
|
69
|
+
const bg = isSelected ? theme.bg.active : undefined;
|
|
70
|
+
const fg = isSelected ? theme.text.inverse : theme.text.primary;
|
|
71
|
+
const prefix = isSelected ? "\u203a " : " ";
|
|
72
|
+
return (_jsx(Box, { backgroundColor: bg, children: _jsxs(Text, { color: fg, children: [prefix, label] }) }, label));
|
|
73
|
+
}), _jsxs(Text, { color: theme.text.muted, children: ["\n", "or press 'n' to create a custom loop"] }), _jsx(Text, { color: theme.text.muted, children: "press 'h' for keyboard shortcuts (from main view)" }), creating ? (_jsxs(Text, { color: theme.accent.brand, children: ["\n", "Creating: ", creating, "..."] })) : null] }) }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { t } from "../../shared/i18n/index.js";
|
|
3
|
+
import { resolveInputOwner } from "../../shared/ui/state.js";
|
|
4
|
+
export function useOverlayStack(context) {
|
|
5
|
+
const { confirmState, setConfirmState, searchState, setSearchState, setSearchValue, logModalRun, setLogModalRun, logModalLoopId: _logModalLoopId, setLogModalLoopId, commandsBrowserOpen, setCommandsBrowserOpen, exportModal, setExportModal, diagramModal, setDiagramModal, contextHelpOpen, setContextHelpOpen, view, pop, onQuit, exit, commandBarHasText, commandBarDropdownOpen, } = context;
|
|
6
|
+
const popLayer = () => {
|
|
7
|
+
if (confirmState) {
|
|
8
|
+
setConfirmState(null);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
if (searchState?.active) {
|
|
12
|
+
setSearchState(null);
|
|
13
|
+
setSearchValue("");
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (logModalRun) {
|
|
17
|
+
setLogModalRun(null);
|
|
18
|
+
setLogModalLoopId(null);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (commandsBrowserOpen) {
|
|
22
|
+
setCommandsBrowserOpen(false);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (exportModal) {
|
|
26
|
+
setExportModal(null);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (diagramModal) {
|
|
30
|
+
setDiagramModal(null);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (contextHelpOpen) {
|
|
34
|
+
setContextHelpOpen(false);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (view !== "board") {
|
|
38
|
+
pop();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
setConfirmState({
|
|
42
|
+
prompt: t("confirm.quit"),
|
|
43
|
+
onConfirm: () => { onQuit(); exit(); },
|
|
44
|
+
});
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
const anyModalOpen = !!(logModalRun || commandsBrowserOpen || exportModal || diagramModal);
|
|
48
|
+
const commandInputDisabled = anyModalOpen;
|
|
49
|
+
const inputOwner = useMemo(() => resolveInputOwner({
|
|
50
|
+
modalOpen: !!(logModalRun || commandsBrowserOpen || exportModal || diagramModal || contextHelpOpen || confirmState || searchState?.active),
|
|
51
|
+
commandBarHasText,
|
|
52
|
+
commandBarDropdownOpen,
|
|
53
|
+
}), [logModalRun, commandsBrowserOpen, exportModal, diagramModal, contextHelpOpen, confirmState, searchState?.active, commandBarHasText, commandBarDropdownOpen]);
|
|
54
|
+
return { popLayer, anyModalOpen, commandInputDisabled, inputOwner };
|
|
55
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useState, useMemo, useEffect, useCallback } from "react";
|
|
2
|
+
import { applyLoopFilters, defaultFilters } from "../../entities/loops/filters.js";
|
|
3
|
+
import { applyProjectFilters, defaultProjectFilters } from "../../entities/projects/filters.js";
|
|
4
|
+
import { POLL_MS } from "../../shared/config/constants.js";
|
|
5
|
+
export function useAppState(loops, pushToast, refresh, loopService, taskService, projectService, logService, _view, _push, _pop) {
|
|
6
|
+
const [activeTab, setActiveTab] = useState("loops");
|
|
7
|
+
const [focusedPanel, setFocusedPanel] = useState("left");
|
|
8
|
+
const [filters, setFilters] = useState(defaultFilters);
|
|
9
|
+
const [sort, setSort] = useState("description");
|
|
10
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
11
|
+
const [editTarget, setEditTarget] = useState(null);
|
|
12
|
+
const [cloneMode, setCloneMode] = useState(false);
|
|
13
|
+
const [editTask, setEditTask] = useState(null);
|
|
14
|
+
const [editProject, setEditProject] = useState(null);
|
|
15
|
+
const [pendingTaskSelection, setPendingTaskSelection] = useState(null);
|
|
16
|
+
const [selectedRunIndex, setSelectedRunIndex] = useState(0);
|
|
17
|
+
const [logModalRun, setLogModalRun] = useState(null);
|
|
18
|
+
const [logModalLoopId, setLogModalLoopId] = useState(null);
|
|
19
|
+
const [logModalLines, setLogModalLines] = useState([]);
|
|
20
|
+
const [logModalLoading, setLogModalLoading] = useState(false);
|
|
21
|
+
const [tasks, setTasks] = useState([]);
|
|
22
|
+
const [taskSelectedIndex, setTaskSelectedIndex] = useState(0);
|
|
23
|
+
const [taskQuery, setTaskQuery] = useState("");
|
|
24
|
+
const [projects, setProjects] = useState([]);
|
|
25
|
+
const [currentProjectId, setCurrentProjectId] = useState("all");
|
|
26
|
+
const [projectSelectedIndex, setProjectSelectedIndex] = useState(0);
|
|
27
|
+
const [projectFilters, setProjectFilters] = useState(defaultProjectFilters);
|
|
28
|
+
const [commandsBrowserOpen, setCommandsBrowserOpen] = useState(false);
|
|
29
|
+
const [contextHelpOpen, setContextHelpOpen] = useState(false);
|
|
30
|
+
const [exportModal, setExportModal] = useState(null);
|
|
31
|
+
const [diagramModal, setDiagramModal] = useState(null);
|
|
32
|
+
const [confirmState, setConfirmState] = useState(null);
|
|
33
|
+
const [searchState, setSearchState] = useState(null);
|
|
34
|
+
const [searchValue, setSearchValue] = useState("");
|
|
35
|
+
const [debugMode, setDebugMode] = useState(false);
|
|
36
|
+
const [debugEntries, setDebugEntries] = useState([]);
|
|
37
|
+
const [chordState, setChordState] = useState(null);
|
|
38
|
+
const [commandBarHasText, setCommandBarHasText] = useState(false);
|
|
39
|
+
const [commandBarDropdownOpen, setCommandBarDropdownOpen] = useState(false);
|
|
40
|
+
const visible = useMemo(() => applyLoopFilters(currentProjectId === "all" ? loops : loops.filter((l) => (l.projectId ?? "default") === currentProjectId), filters, sort, projects), [loops, filters, sort, currentProjectId, projects]);
|
|
41
|
+
const clampedIndex = Math.min(selectedIndex, Math.max(0, visible.length - 1));
|
|
42
|
+
const selected = visible[clampedIndex] ?? null;
|
|
43
|
+
const selectedId = selected?.id ?? null;
|
|
44
|
+
useEffect(() => { setSelectedRunIndex(0); }, [selected?.id]);
|
|
45
|
+
const filteredTasks = useMemo(() => {
|
|
46
|
+
const sorted = [...tasks].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }));
|
|
47
|
+
if (!taskQuery)
|
|
48
|
+
return sorted;
|
|
49
|
+
const q = taskQuery.toLowerCase();
|
|
50
|
+
return sorted.filter((t) => `${t.id} ${t.name} ${t.command}`.toLowerCase().includes(q));
|
|
51
|
+
}, [tasks, taskQuery]);
|
|
52
|
+
const taskClampedIndex = Math.min(taskSelectedIndex, Math.max(0, filteredTasks.length - 1));
|
|
53
|
+
const selectedTask = filteredTasks[taskClampedIndex] ?? null;
|
|
54
|
+
const filteredProjects = useMemo(() => applyProjectFilters(projects, loops, projectFilters), [projects, loops, projectFilters]);
|
|
55
|
+
const projectClampedIndex = Math.min(projectSelectedIndex, Math.max(0, filteredProjects.length - 1));
|
|
56
|
+
const selectedProjectEntity = filteredProjects[projectClampedIndex] ?? null;
|
|
57
|
+
const projectLoopCount = selectedProjectEntity
|
|
58
|
+
? loops.filter((l) => (l.projectId ?? "default") === selectedProjectEntity.id).length
|
|
59
|
+
: 0;
|
|
60
|
+
async function refreshTasks() {
|
|
61
|
+
try {
|
|
62
|
+
setTasks(await taskService.list());
|
|
63
|
+
}
|
|
64
|
+
catch { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
async function refreshProjects() {
|
|
67
|
+
try {
|
|
68
|
+
setProjects(await projectService.list());
|
|
69
|
+
}
|
|
70
|
+
catch { /* ignore */ }
|
|
71
|
+
}
|
|
72
|
+
useEffect(() => { void refreshTasks(); void refreshProjects(); }, []);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const timer = setInterval(() => { void refreshTasks(); void refreshProjects(); }, POLL_MS);
|
|
75
|
+
return () => clearInterval(timer);
|
|
76
|
+
}, []);
|
|
77
|
+
function runAction(label, action) {
|
|
78
|
+
return async () => {
|
|
79
|
+
try {
|
|
80
|
+
await action();
|
|
81
|
+
void refresh();
|
|
82
|
+
pushToast("success", label);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
pushToast("error", error instanceof Error ? error.message : String(error));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function handleOpenRunLog(run) {
|
|
90
|
+
if (!selectedId)
|
|
91
|
+
return;
|
|
92
|
+
setLogModalRun(run);
|
|
93
|
+
setLogModalLoopId(selectedId);
|
|
94
|
+
if (run.status === "running") {
|
|
95
|
+
setLogModalLoading(false);
|
|
96
|
+
setLogModalLines([]);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
setLogModalLoading(true);
|
|
100
|
+
setLogModalLines([]);
|
|
101
|
+
logService.fetchRunLog(selectedId, run.runNumber)
|
|
102
|
+
.then((log) => { setLogModalLines(log ? log.split("\n").map((l) => l.replace(/\r$/, "")) : []); setLogModalLoading(false); })
|
|
103
|
+
.catch((err) => { setLogModalLines([]); setLogModalLoading(false); pushToast("error", err.message ?? "Failed to load log"); });
|
|
104
|
+
}
|
|
105
|
+
const leftPanelQuery = searchState?.active ? searchValue : activeTab === "tasks" ? taskQuery : filters.query;
|
|
106
|
+
const handleSearchChange = useCallback((value) => {
|
|
107
|
+
setSearchValue(value);
|
|
108
|
+
if (activeTab === "tasks")
|
|
109
|
+
setTaskQuery(value);
|
|
110
|
+
else
|
|
111
|
+
setFilters((prev) => ({ ...prev, query: value }));
|
|
112
|
+
}, [activeTab]);
|
|
113
|
+
const handleSearchSubmit = useCallback(() => { setSearchState(null); }, []);
|
|
114
|
+
const handleSearchCancel = useCallback(() => {
|
|
115
|
+
setSearchValue("");
|
|
116
|
+
if (activeTab === "tasks")
|
|
117
|
+
setTaskQuery("");
|
|
118
|
+
else
|
|
119
|
+
setFilters((prev) => ({ ...prev, query: "" }));
|
|
120
|
+
setSearchState(null);
|
|
121
|
+
}, [activeTab]);
|
|
122
|
+
const counts = {
|
|
123
|
+
total: loops.length, running: loops.filter((l) => l.status === "running").length,
|
|
124
|
+
waiting: loops.filter((l) => l.status === "waiting").length, paused: loops.filter((l) => l.status === "paused").length,
|
|
125
|
+
idle: loops.filter((l) => l.status === "idle").length,
|
|
126
|
+
};
|
|
127
|
+
const tabCounts = { loops: loops.length, tasks: tasks.length, projects: projects.length };
|
|
128
|
+
return {
|
|
129
|
+
activeTab, setActiveTab, focusedPanel, setFocusedPanel,
|
|
130
|
+
filters, setFilters, sort, setSort, selectedIndex, setSelectedIndex,
|
|
131
|
+
editTarget, setEditTarget, cloneMode, setCloneMode,
|
|
132
|
+
editTask, setEditTask, editProject, setEditProject,
|
|
133
|
+
pendingTaskSelection, setPendingTaskSelection, selectedRunIndex, setSelectedRunIndex,
|
|
134
|
+
logModalRun, setLogModalRun, logModalLoopId, setLogModalLoopId,
|
|
135
|
+
logModalLines, setLogModalLines, logModalLoading, setLogModalLoading,
|
|
136
|
+
tasks, setTasks, taskSelectedIndex, setTaskSelectedIndex, taskQuery, setTaskQuery,
|
|
137
|
+
projects, setProjects, currentProjectId, setCurrentProjectId,
|
|
138
|
+
projectSelectedIndex, setProjectSelectedIndex, projectFilters, setProjectFilters,
|
|
139
|
+
commandsBrowserOpen, setCommandsBrowserOpen, contextHelpOpen, setContextHelpOpen,
|
|
140
|
+
exportModal, setExportModal, diagramModal, setDiagramModal, confirmState, setConfirmState,
|
|
141
|
+
searchState, setSearchState, searchValue, setSearchValue,
|
|
142
|
+
debugMode, setDebugMode, debugEntries, setDebugEntries,
|
|
143
|
+
chordState, setChordState, commandBarHasText, setCommandBarHasText, commandBarDropdownOpen, setCommandBarDropdownOpen,
|
|
144
|
+
visible, clampedIndex, selected, selectedId,
|
|
145
|
+
filteredTasks, taskClampedIndex, selectedTask,
|
|
146
|
+
filteredProjects, projectClampedIndex, selectedProjectEntity, projectLoopCount,
|
|
147
|
+
refreshTasks, refreshProjects, runAction, handleOpenRunLog,
|
|
148
|
+
leftPanelQuery, handleSearchChange, handleSearchSubmit, handleSearchCancel,
|
|
149
|
+
counts, tabCounts,
|
|
150
|
+
};
|
|
151
|
+
}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class Logger {
|
|
2
|
+
constructor(verbose = false) {
|
|
3
|
+
this.verbose = verbose;
|
|
4
|
+
}
|
|
5
|
+
info(message) {
|
|
6
|
+
console.log(message);
|
|
7
|
+
}
|
|
8
|
+
error(message) {
|
|
9
|
+
console.error(message);
|
|
10
|
+
}
|
|
11
|
+
success(message) {
|
|
12
|
+
console.log(message);
|
|
13
|
+
}
|
|
14
|
+
debug(message) {
|
|
15
|
+
if (this.verbose) {
|
|
16
|
+
console.log(`[verbose] ${message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
warn(message) {
|
|
20
|
+
console.warn(message);
|
|
21
|
+
}
|
|
22
|
+
timestamp() {
|
|
23
|
+
return new Date().toISOString();
|
|
24
|
+
}
|
|
25
|
+
}
|