@compilr-dev/cli 0.4.0 → 0.5.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 +30 -12
- package/dist/agent.d.ts +74 -1
- package/dist/agent.js +259 -76
- package/dist/anchors/index.d.ts +9 -0
- package/dist/anchors/index.js +9 -0
- package/dist/anchors/project-anchors.d.ts +79 -0
- package/dist/anchors/project-anchors.js +202 -0
- package/dist/commands/handler-types.d.ts +68 -0
- package/dist/commands/handler-types.js +8 -0
- package/dist/commands/handlers/agent-commands.d.ts +13 -0
- package/dist/commands/handlers/agent-commands.js +305 -0
- package/dist/commands/handlers/design-commands.d.ts +15 -0
- package/dist/commands/handlers/design-commands.js +334 -0
- package/dist/commands/handlers/index.d.ts +20 -0
- package/dist/commands/handlers/index.js +43 -0
- package/dist/commands/handlers/overlay-commands.d.ts +21 -0
- package/dist/commands/handlers/overlay-commands.js +287 -0
- package/dist/commands/handlers/project-commands.d.ts +11 -0
- package/dist/commands/handlers/project-commands.js +167 -0
- package/dist/commands/handlers/simple-commands.d.ts +19 -0
- package/dist/commands/handlers/simple-commands.js +144 -0
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/registry.d.ts +50 -0
- package/dist/commands/registry.js +75 -0
- package/dist/commands-v2/handlers/context.d.ts +13 -0
- package/dist/commands-v2/handlers/context.js +348 -0
- package/dist/commands-v2/handlers/core.d.ts +13 -0
- package/dist/commands-v2/handlers/core.js +165 -0
- package/dist/commands-v2/handlers/debug.d.ts +11 -0
- package/dist/commands-v2/handlers/debug.js +159 -0
- package/dist/commands-v2/handlers/index.d.ts +12 -0
- package/dist/commands-v2/handlers/index.js +24 -0
- package/dist/commands-v2/handlers/project.d.ts +22 -0
- package/dist/commands-v2/handlers/project.js +814 -0
- package/dist/commands-v2/handlers/settings.d.ts +15 -0
- package/dist/commands-v2/handlers/settings.js +235 -0
- package/dist/commands-v2/index.d.ts +13 -0
- package/dist/commands-v2/index.js +15 -0
- package/dist/commands-v2/registry.d.ts +37 -0
- package/dist/commands-v2/registry.js +80 -0
- package/dist/commands-v2/types.d.ts +75 -0
- package/dist/commands-v2/types.js +7 -0
- package/dist/commands.js +110 -7
- package/dist/index.js +288 -29
- package/dist/input-handlers/index.d.ts +7 -0
- package/dist/input-handlers/index.js +7 -0
- package/dist/input-handlers/memory-handler.d.ts +26 -0
- package/dist/input-handlers/memory-handler.js +68 -0
- package/dist/repl-helpers.d.ts +63 -0
- package/dist/repl-helpers.js +318 -0
- package/dist/repl-v2.d.ts +155 -0
- package/dist/repl-v2.js +774 -0
- package/dist/repl.d.ts +32 -4
- package/dist/repl.js +250 -977
- package/dist/settings/index.d.ts +23 -0
- package/dist/settings/index.js +48 -0
- package/dist/settings/paths.d.ts +110 -0
- package/dist/settings/paths.js +264 -0
- package/dist/templates/compilr-md.js +7 -4
- package/dist/templates/index.js +3 -4
- package/dist/themes/colors.js +3 -1
- package/dist/themes/registry.d.ts +5 -36
- package/dist/themes/registry.js +11 -95
- package/dist/themes/types.d.ts +3 -38
- package/dist/themes/types.js +2 -2
- package/dist/tools/anchor-tools.d.ts +31 -0
- package/dist/tools/anchor-tools.js +255 -0
- package/dist/tools/backlog-wrappers.d.ts +54 -0
- package/dist/tools/backlog-wrappers.js +338 -0
- package/dist/tools/backlog.js +1 -1
- package/dist/tools/db-tools.d.ts +65 -0
- package/dist/tools/db-tools.js +19 -0
- package/dist/tools/document-db.d.ts +43 -0
- package/dist/tools/document-db.js +220 -0
- package/dist/tools/project-db.d.ts +102 -0
- package/dist/tools/project-db.js +370 -0
- package/dist/tools/workitem-db.d.ts +103 -0
- package/dist/tools/workitem-db.js +549 -0
- package/dist/tools.js +13 -3
- package/dist/ui/agents-overlay-v2.d.ts +43 -0
- package/dist/ui/agents-overlay-v2.js +809 -0
- package/dist/ui/agents-overlay.d.ts +5 -5
- package/dist/ui/agents-overlay.js +782 -420
- package/dist/ui/anchors-overlay.d.ts +12 -0
- package/dist/ui/anchors-overlay.js +775 -0
- package/dist/ui/arch-type-overlay.d.ts +1 -6
- package/dist/ui/arch-type-overlay.js +175 -203
- package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
- package/dist/ui/ask-user-overlay-v2.js +555 -0
- package/dist/ui/ask-user-overlay.d.ts +2 -2
- package/dist/ui/ask-user-overlay.js +443 -535
- package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
- package/dist/ui/ask-user-simple-overlay.js +182 -209
- package/dist/ui/backlog-overlay.d.ts +16 -1
- package/dist/ui/backlog-overlay.js +525 -659
- package/dist/ui/base/index.d.ts +26 -0
- package/dist/ui/base/index.js +33 -0
- package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
- package/dist/ui/base/inline-overlay-utils.js +320 -0
- package/dist/ui/base/inline-overlay.d.ts +159 -0
- package/dist/ui/base/inline-overlay.js +257 -0
- package/dist/ui/base/key-utils.d.ts +15 -0
- package/dist/ui/base/key-utils.js +30 -0
- package/dist/ui/base/overlay-base-v2.d.ts +193 -0
- package/dist/ui/base/overlay-base-v2.js +246 -0
- package/dist/ui/base/overlay-base.d.ts +156 -0
- package/dist/ui/base/overlay-base.js +238 -0
- package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
- package/dist/ui/base/overlay-lifecycle.js +159 -0
- package/dist/ui/base/overlay-types.d.ts +185 -0
- package/dist/ui/base/overlay-types.js +7 -0
- package/dist/ui/base/render-utils.d.ts +8 -0
- package/dist/ui/base/render-utils.js +11 -0
- package/dist/ui/base/screen-stack.d.ts +148 -0
- package/dist/ui/base/screen-stack.js +184 -0
- package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
- package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
- package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
- package/dist/ui/base/tabbed-list-overlay.js +369 -0
- package/dist/ui/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/commands-overlay-v2.js +441 -0
- package/dist/ui/commands-overlay.d.ts +7 -2
- package/dist/ui/commands-overlay.js +384 -355
- package/dist/ui/config-overlay.d.ts +5 -4
- package/dist/ui/config-overlay.js +243 -513
- package/dist/ui/conversation.d.ts +75 -4
- package/dist/ui/conversation.js +374 -161
- package/dist/ui/docs-overlay.d.ts +17 -0
- package/dist/ui/docs-overlay.js +303 -0
- package/dist/ui/ephemeral.d.ts +1 -1
- package/dist/ui/ephemeral.js +1 -1
- package/dist/ui/features/index.d.ts +34 -0
- package/dist/ui/features/index.js +34 -0
- package/dist/ui/features/input-feature.d.ts +85 -0
- package/dist/ui/features/input-feature.js +238 -0
- package/dist/ui/features/list-feature.d.ts +155 -0
- package/dist/ui/features/list-feature.js +244 -0
- package/dist/ui/features/pagination-feature.d.ts +154 -0
- package/dist/ui/features/pagination-feature.js +238 -0
- package/dist/ui/features/search-feature.d.ts +148 -0
- package/dist/ui/features/search-feature.js +185 -0
- package/dist/ui/features/tab-feature.d.ts +194 -0
- package/dist/ui/features/tab-feature.js +307 -0
- package/dist/ui/footer-v2.d.ts +222 -0
- package/dist/ui/footer-v2.js +1349 -0
- package/dist/ui/footer.d.ts +107 -0
- package/dist/ui/footer.js +359 -67
- package/dist/ui/guardrail-overlay.d.ts +29 -0
- package/dist/ui/guardrail-overlay.js +145 -0
- package/dist/ui/help-overlay-v2.d.ts +34 -0
- package/dist/ui/help-overlay-v2.js +309 -0
- package/dist/ui/help-overlay.d.ts +16 -0
- package/dist/ui/help-overlay.js +316 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -3
- package/dist/ui/init-overlay-v2.d.ts +34 -0
- package/dist/ui/init-overlay-v2.js +600 -0
- package/dist/ui/init-overlay.d.ts +12 -2
- package/dist/ui/init-overlay.js +349 -270
- package/dist/ui/input-prompt-v2.d.ts +1 -0
- package/dist/ui/input-prompt-v2.js +14 -6
- package/dist/ui/input-prompt.d.ts +116 -33
- package/dist/ui/input-prompt.js +536 -337
- package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay-v2.js +114 -0
- package/dist/ui/iteration-limit-overlay.d.ts +2 -2
- package/dist/ui/iteration-limit-overlay.js +92 -128
- package/dist/ui/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/keys-overlay-v2.js +248 -0
- package/dist/ui/keys-overlay.d.ts +1 -0
- package/dist/ui/keys-overlay.js +203 -141
- package/dist/ui/line-utils.d.ts +88 -0
- package/dist/ui/line-utils.js +150 -0
- package/dist/ui/live-region.d.ts +161 -0
- package/dist/ui/live-region.js +387 -0
- package/dist/ui/mascot/expressions.d.ts +32 -0
- package/dist/ui/mascot/expressions.js +213 -0
- package/dist/ui/mascot/index.d.ts +8 -0
- package/dist/ui/mascot/index.js +8 -0
- package/dist/ui/mascot/renderer.d.ts +19 -0
- package/dist/ui/mascot/renderer.js +97 -0
- package/dist/ui/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/mascot-overlay-v2.js +138 -0
- package/dist/ui/mascot-overlay.d.ts +21 -0
- package/dist/ui/mascot-overlay.js +146 -0
- package/dist/ui/model-overlay-v2.d.ts +49 -0
- package/dist/ui/model-overlay-v2.js +118 -0
- package/dist/ui/model-overlay.d.ts +27 -0
- package/dist/ui/model-overlay.js +221 -0
- package/dist/ui/model-warning-overlay.js +3 -5
- package/dist/ui/new-overlay.d.ts +34 -0
- package/dist/ui/new-overlay.js +604 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
- package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
- package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
- package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
- package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
- package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
- package/dist/ui/overlay/index.d.ts +33 -0
- package/dist/ui/overlay/index.js +35 -0
- package/dist/ui/overlay/key-utils.d.ts +6 -0
- package/dist/ui/overlay/key-utils.js +6 -0
- package/dist/ui/overlay/overlay-types.d.ts +128 -0
- package/dist/ui/overlay/overlay-types.js +22 -0
- package/dist/ui/overlay/types.d.ts +135 -0
- package/dist/ui/overlay/types.js +22 -0
- package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
- package/dist/ui/overlays/help-overlay-v2.js +198 -0
- package/dist/ui/overlays/index.d.ts +11 -0
- package/dist/ui/overlays/index.js +11 -0
- package/dist/ui/overlays.d.ts +0 -4
- package/dist/ui/overlays.js +0 -444
- package/dist/ui/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/permission-overlay-v2.js +380 -0
- package/dist/ui/permission-overlay.d.ts +1 -1
- package/dist/ui/permission-overlay.js +186 -298
- package/dist/ui/projects-overlay.d.ts +19 -0
- package/dist/ui/projects-overlay.js +484 -0
- package/dist/ui/providers/types.d.ts +178 -0
- package/dist/ui/providers/types.js +9 -0
- package/dist/ui/render-modes.d.ts +36 -0
- package/dist/ui/render-modes.js +44 -0
- package/dist/ui/startup-menu.d.ts +36 -0
- package/dist/ui/startup-menu.js +236 -0
- package/dist/ui/subagent-renderer.d.ts +117 -0
- package/dist/ui/subagent-renderer.js +334 -0
- package/dist/ui/terminal-codes.d.ts +94 -0
- package/dist/ui/terminal-codes.js +124 -0
- package/dist/ui/terminal-renderer.d.ts +221 -0
- package/dist/ui/terminal-renderer.js +751 -0
- package/dist/ui/terminal-ui.d.ts +463 -0
- package/dist/ui/terminal-ui.js +2296 -0
- package/dist/ui/terminal.d.ts +20 -0
- package/dist/ui/terminal.js +72 -0
- package/dist/ui/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/theme-overlay-v2.js +135 -0
- package/dist/ui/theme-overlay.d.ts +24 -0
- package/dist/ui/theme-overlay.js +127 -0
- package/dist/ui/todo-zone.js +53 -25
- package/dist/ui/tool-formatters.d.ts +16 -0
- package/dist/ui/tool-formatters.js +516 -0
- package/dist/ui/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/tools-overlay-v2.js +218 -0
- package/dist/ui/tools-overlay.d.ts +10 -2
- package/dist/ui/tools-overlay.js +172 -220
- package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/tutorial-overlay.d.ts +1 -0
- package/dist/ui/tutorial-overlay.js +400 -302
- package/dist/ui/workflow-overlay.d.ts +22 -0
- package/dist/ui/workflow-overlay.js +636 -0
- package/dist/utils/debug-log.d.ts +28 -0
- package/dist/utils/debug-log.js +57 -0
- package/dist/utils/model-tiers.js +1 -1
- package/dist/utils/path-safety.d.ts +56 -0
- package/dist/utils/path-safety.js +239 -0
- package/dist/workflow/guided-mode-injector.d.ts +42 -0
- package/dist/workflow/guided-mode-injector.js +191 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/step-criteria.d.ts +62 -0
- package/dist/workflow/step-criteria.js +150 -0
- package/dist/workflow/step-tracker.d.ts +92 -0
- package/dist/workflow/step-tracker.js +141 -0
- package/package.json +12 -5
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchors Overlay
|
|
3
|
+
*
|
|
4
|
+
* Provides a UI for viewing and managing anchors:
|
|
5
|
+
* - View all anchors (global + project-specific)
|
|
6
|
+
* - Add new anchors
|
|
7
|
+
* - Edit existing anchors
|
|
8
|
+
* - Delete anchors
|
|
9
|
+
*
|
|
10
|
+
* Uses TabbedListOverlay for consistent tabbed list behavior.
|
|
11
|
+
*/
|
|
12
|
+
import * as terminal from './terminal.js';
|
|
13
|
+
import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isEnter, isCtrlC, isEscape, isClose, isBackspace, isNavigateUp, isNavigateDown, isLeftArrow, isRightArrow, isTab, isShiftTab, renderBorder, } from './base/index.js';
|
|
14
|
+
import { getGlobalAnchorManager, getAnchorManager, } from '../anchors/index.js';
|
|
15
|
+
import { getActiveProject } from '../tools/project-db.js';
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Constants
|
|
18
|
+
// =============================================================================
|
|
19
|
+
const PAGE_SIZE = 10;
|
|
20
|
+
const MAX_ANCHOR_LENGTH = 500;
|
|
21
|
+
const PRIORITIES = ['critical', 'safety', 'info'];
|
|
22
|
+
const TABS = [
|
|
23
|
+
{ id: 'all', label: 'All' },
|
|
24
|
+
{ id: 'critical', label: 'Critical' },
|
|
25
|
+
{ id: 'safety', label: 'Safety' },
|
|
26
|
+
{ id: 'info', label: 'Info' },
|
|
27
|
+
{ id: 'global', label: 'Global' },
|
|
28
|
+
{ id: 'project', label: 'Project' },
|
|
29
|
+
];
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Helper Functions
|
|
32
|
+
// =============================================================================
|
|
33
|
+
function getPriorityBadge(priority, s) {
|
|
34
|
+
switch (priority) {
|
|
35
|
+
case 'critical':
|
|
36
|
+
return s.error('[!]');
|
|
37
|
+
case 'safety':
|
|
38
|
+
return s.warning('[S]');
|
|
39
|
+
case 'info':
|
|
40
|
+
return s.muted('[i]');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isWordChar(char) {
|
|
44
|
+
return /\w/.test(char);
|
|
45
|
+
}
|
|
46
|
+
function findPrevWordBoundary(text, cursor) {
|
|
47
|
+
if (cursor <= 0)
|
|
48
|
+
return 0;
|
|
49
|
+
let pos = cursor - 1;
|
|
50
|
+
while (pos > 0 && !isWordChar(text[pos])) {
|
|
51
|
+
pos--;
|
|
52
|
+
}
|
|
53
|
+
while (pos > 0 && isWordChar(text[pos - 1])) {
|
|
54
|
+
pos--;
|
|
55
|
+
}
|
|
56
|
+
return pos;
|
|
57
|
+
}
|
|
58
|
+
function findNextWordBoundary(text, cursor) {
|
|
59
|
+
if (cursor >= text.length)
|
|
60
|
+
return text.length;
|
|
61
|
+
let pos = cursor;
|
|
62
|
+
while (pos < text.length && isWordChar(text[pos])) {
|
|
63
|
+
pos++;
|
|
64
|
+
}
|
|
65
|
+
while (pos < text.length && !isWordChar(text[pos])) {
|
|
66
|
+
pos++;
|
|
67
|
+
}
|
|
68
|
+
return pos;
|
|
69
|
+
}
|
|
70
|
+
function extractPrintable(data) {
|
|
71
|
+
if (data.length === 1 && data[0] >= 0x20 && data[0] < 0x7f) {
|
|
72
|
+
return String.fromCharCode(data[0]);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Add Anchor Screen
|
|
78
|
+
// =============================================================================
|
|
79
|
+
class AddAnchorScreen extends BaseScreen {
|
|
80
|
+
editState;
|
|
81
|
+
styles;
|
|
82
|
+
hasProject;
|
|
83
|
+
activeProjectId;
|
|
84
|
+
onComplete;
|
|
85
|
+
constructor(editState, styles, hasProject, activeProjectId, onComplete) {
|
|
86
|
+
super();
|
|
87
|
+
this.editState = editState;
|
|
88
|
+
this.styles = styles;
|
|
89
|
+
this.hasProject = hasProject;
|
|
90
|
+
this.activeProjectId = activeProjectId;
|
|
91
|
+
this.onComplete = onComplete;
|
|
92
|
+
}
|
|
93
|
+
render() {
|
|
94
|
+
const s = this.styles;
|
|
95
|
+
const cols = terminal.getTerminalWidth();
|
|
96
|
+
const border = renderBorder(cols, s);
|
|
97
|
+
const lines = [];
|
|
98
|
+
lines.push(border);
|
|
99
|
+
lines.push(` ${s.primaryBold('ADD NEW ANCHOR')}`);
|
|
100
|
+
lines.push('');
|
|
101
|
+
// Content field
|
|
102
|
+
const contentLabel = this.editState.addField === 'content' ? s.primary('Content:') : s.muted('Content:');
|
|
103
|
+
lines.push(` ${contentLabel}`);
|
|
104
|
+
const maxDisplayLen = cols - 6;
|
|
105
|
+
let displayContent = this.editState.addContent;
|
|
106
|
+
if (displayContent.length > maxDisplayLen) {
|
|
107
|
+
displayContent = '…' + displayContent.slice(-(maxDisplayLen - 1));
|
|
108
|
+
}
|
|
109
|
+
lines.push(` ${displayContent}${this.editState.addField === 'content' ? '█' : ''}`);
|
|
110
|
+
lines.push('');
|
|
111
|
+
// Priority field
|
|
112
|
+
const priorityLabel = this.editState.addField === 'priority' ? s.primary('Priority:') : s.muted('Priority:');
|
|
113
|
+
const priorityOptions = PRIORITIES.map(p => p === this.editState.addPriority ? s.inverse(` ${p} `) : s.muted(` ${p} `)).join(' ');
|
|
114
|
+
lines.push(` ${priorityLabel} ${priorityOptions}`);
|
|
115
|
+
// Scope field (only if project is active)
|
|
116
|
+
if (this.hasProject) {
|
|
117
|
+
const scopeLabel = this.editState.addField === 'scope' ? s.primary('Scope:') : s.muted('Scope:');
|
|
118
|
+
const scopeOptions = ['global', 'project'].map(sc => sc === this.editState.addScope ? s.inverse(` ${sc} `) : s.muted(` ${sc} `)).join(' ');
|
|
119
|
+
lines.push(` ${scopeLabel} ${scopeOptions}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push('');
|
|
122
|
+
const remaining = MAX_ANCHOR_LENGTH - this.editState.addContent.length;
|
|
123
|
+
lines.push(s.muted(` ${String(remaining)} chars remaining`));
|
|
124
|
+
// Message
|
|
125
|
+
if (this.editState.message) {
|
|
126
|
+
lines.push('');
|
|
127
|
+
switch (this.editState.messageType) {
|
|
128
|
+
case 'success':
|
|
129
|
+
lines.push(s.success(` ${this.editState.message}`));
|
|
130
|
+
break;
|
|
131
|
+
case 'error':
|
|
132
|
+
lines.push(s.error(` ${this.editState.message}`));
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
lines.push(s.muted(` ${this.editState.message}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push(border);
|
|
140
|
+
lines.push(s.muted(' Tab Switch field · ↑↓/←→ Change value · Enter Save · Esc Cancel'));
|
|
141
|
+
lines.push(border);
|
|
142
|
+
return lines;
|
|
143
|
+
}
|
|
144
|
+
handleKey(data) {
|
|
145
|
+
const char = extractPrintable(data);
|
|
146
|
+
if (isCtrlC(data)) {
|
|
147
|
+
return closeOverlay(undefined);
|
|
148
|
+
}
|
|
149
|
+
if (isEscape(data)) {
|
|
150
|
+
this.editState.message = null;
|
|
151
|
+
return popScreen();
|
|
152
|
+
}
|
|
153
|
+
// Tab - switch fields
|
|
154
|
+
if (isTab(data)) {
|
|
155
|
+
if (this.editState.addField === 'content') {
|
|
156
|
+
this.editState.addField = 'priority';
|
|
157
|
+
}
|
|
158
|
+
else if (this.editState.addField === 'priority') {
|
|
159
|
+
this.editState.addField = this.hasProject ? 'scope' : 'content';
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.editState.addField = 'content';
|
|
163
|
+
}
|
|
164
|
+
return stay();
|
|
165
|
+
}
|
|
166
|
+
// Shift+Tab - switch fields backward
|
|
167
|
+
if (isShiftTab(data)) {
|
|
168
|
+
if (this.editState.addField === 'content') {
|
|
169
|
+
this.editState.addField = this.hasProject ? 'scope' : 'priority';
|
|
170
|
+
}
|
|
171
|
+
else if (this.editState.addField === 'priority') {
|
|
172
|
+
this.editState.addField = 'content';
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.editState.addField = 'priority';
|
|
176
|
+
}
|
|
177
|
+
return stay();
|
|
178
|
+
}
|
|
179
|
+
// Enter - save
|
|
180
|
+
if (isEnter(data)) {
|
|
181
|
+
if (this.editState.addContent.trim()) {
|
|
182
|
+
try {
|
|
183
|
+
const manager = this.editState.addScope === 'global'
|
|
184
|
+
? getGlobalAnchorManager()
|
|
185
|
+
: getAnchorManager(String(this.activeProjectId));
|
|
186
|
+
manager.add({
|
|
187
|
+
content: this.editState.addContent.trim(),
|
|
188
|
+
priority: this.editState.addPriority,
|
|
189
|
+
scope: 'persistent',
|
|
190
|
+
});
|
|
191
|
+
this.onComplete();
|
|
192
|
+
this.editState.message = null;
|
|
193
|
+
return popScreen();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
this.editState.message = `Error: ${error.message}`;
|
|
197
|
+
this.editState.messageType = 'error';
|
|
198
|
+
return stay();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
this.editState.message = 'Content cannot be empty';
|
|
203
|
+
this.editState.messageType = 'error';
|
|
204
|
+
return stay();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Field-specific input
|
|
208
|
+
if (this.editState.addField === 'content') {
|
|
209
|
+
if (isBackspace(data)) {
|
|
210
|
+
this.editState.addContent = this.editState.addContent.slice(0, -1);
|
|
211
|
+
return stay();
|
|
212
|
+
}
|
|
213
|
+
if (char && this.editState.addContent.length < MAX_ANCHOR_LENGTH) {
|
|
214
|
+
this.editState.addContent += char;
|
|
215
|
+
return stay();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (this.editState.addField === 'priority') {
|
|
219
|
+
if (isNavigateUp(data) || isLeftArrow(data)) {
|
|
220
|
+
const idx = PRIORITIES.indexOf(this.editState.addPriority);
|
|
221
|
+
this.editState.addPriority = PRIORITIES[(idx - 1 + PRIORITIES.length) % PRIORITIES.length];
|
|
222
|
+
return stay();
|
|
223
|
+
}
|
|
224
|
+
if (isNavigateDown(data) || isRightArrow(data)) {
|
|
225
|
+
const idx = PRIORITIES.indexOf(this.editState.addPriority);
|
|
226
|
+
this.editState.addPriority = PRIORITIES[(idx + 1) % PRIORITIES.length];
|
|
227
|
+
return stay();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Scope field
|
|
232
|
+
if (isNavigateUp(data) || isNavigateDown(data) || isLeftArrow(data) || isRightArrow(data)) {
|
|
233
|
+
this.editState.addScope = this.editState.addScope === 'global' ? 'project' : 'global';
|
|
234
|
+
return stay();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return stay(false);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// Edit Anchor Screen
|
|
242
|
+
// =============================================================================
|
|
243
|
+
class EditAnchorScreen extends BaseScreen {
|
|
244
|
+
editState;
|
|
245
|
+
styles;
|
|
246
|
+
onComplete;
|
|
247
|
+
maxVisibleLines = 6;
|
|
248
|
+
constructor(editState, styles, onComplete) {
|
|
249
|
+
super();
|
|
250
|
+
this.editState = editState;
|
|
251
|
+
this.styles = styles;
|
|
252
|
+
this.onComplete = onComplete;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Wrap text to fit within maxWidth, preserving word boundaries where possible
|
|
256
|
+
*/
|
|
257
|
+
wrapText(text, maxWidth) {
|
|
258
|
+
if (text.length <= maxWidth)
|
|
259
|
+
return [text];
|
|
260
|
+
const wrapped = [];
|
|
261
|
+
let remaining = text;
|
|
262
|
+
while (remaining.length > 0) {
|
|
263
|
+
if (remaining.length <= maxWidth) {
|
|
264
|
+
wrapped.push(remaining);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
// Find a good break point (space within last 20 chars of maxWidth)
|
|
268
|
+
let breakPoint = maxWidth;
|
|
269
|
+
const searchStart = Math.max(0, maxWidth - 20);
|
|
270
|
+
for (let i = maxWidth - 1; i >= searchStart; i--) {
|
|
271
|
+
if (remaining[i] === ' ') {
|
|
272
|
+
breakPoint = i + 1;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
wrapped.push(remaining.slice(0, breakPoint));
|
|
277
|
+
remaining = remaining.slice(breakPoint);
|
|
278
|
+
}
|
|
279
|
+
return wrapped;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Convert linear cursor position to wrapped display position (row, col)
|
|
283
|
+
*/
|
|
284
|
+
getWrappedCursorPosition(wrappedLines, linearCursor) {
|
|
285
|
+
let pos = 0;
|
|
286
|
+
for (let row = 0; row < wrappedLines.length; row++) {
|
|
287
|
+
const lineLen = wrappedLines[row].length;
|
|
288
|
+
if (pos + lineLen >= linearCursor) {
|
|
289
|
+
return { row, col: linearCursor - pos };
|
|
290
|
+
}
|
|
291
|
+
pos += lineLen;
|
|
292
|
+
}
|
|
293
|
+
// Cursor at very end
|
|
294
|
+
const lastRow = wrappedLines.length - 1;
|
|
295
|
+
return { row: lastRow, col: wrappedLines[lastRow]?.length ?? 0 };
|
|
296
|
+
}
|
|
297
|
+
render() {
|
|
298
|
+
const s = this.styles;
|
|
299
|
+
const cols = terminal.getTerminalWidth();
|
|
300
|
+
const border = renderBorder(cols, s);
|
|
301
|
+
const lines = [];
|
|
302
|
+
const item = this.editState.targetItem;
|
|
303
|
+
if (!item) {
|
|
304
|
+
lines.push(border);
|
|
305
|
+
lines.push(s.error(' No anchor selected'));
|
|
306
|
+
lines.push(border);
|
|
307
|
+
return lines;
|
|
308
|
+
}
|
|
309
|
+
lines.push(border);
|
|
310
|
+
lines.push(` ${s.primaryBold('EDIT ANCHOR')}`);
|
|
311
|
+
lines.push('');
|
|
312
|
+
lines.push(s.muted(` ID: ${item.anchor.id}`));
|
|
313
|
+
lines.push(s.muted(` Priority: ${item.anchor.priority}`));
|
|
314
|
+
lines.push(s.muted(` Scope: ${item.scope}`));
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push(` ${s.primary('Content:')}`);
|
|
317
|
+
// Wrap content to fit terminal width
|
|
318
|
+
const maxLineWidth = cols - 4;
|
|
319
|
+
const content = this.editState.editContent;
|
|
320
|
+
const cursor = this.editState.editCursor;
|
|
321
|
+
// Wrap the content for display
|
|
322
|
+
const wrappedLines = this.wrapText(content, maxLineWidth);
|
|
323
|
+
const { row: cursorRow, col: cursorCol } = this.getWrappedCursorPosition(wrappedLines, cursor);
|
|
324
|
+
// Calculate visible window (scroll if needed)
|
|
325
|
+
let startLine = 0;
|
|
326
|
+
if (cursorRow >= this.maxVisibleLines) {
|
|
327
|
+
startLine = cursorRow - this.maxVisibleLines + 1;
|
|
328
|
+
}
|
|
329
|
+
const endLine = Math.min(startLine + this.maxVisibleLines, wrappedLines.length);
|
|
330
|
+
for (let i = startLine; i < endLine; i++) {
|
|
331
|
+
const line = wrappedLines[i];
|
|
332
|
+
const isCursorLine = i === cursorRow;
|
|
333
|
+
if (isCursorLine) {
|
|
334
|
+
// Render line with block cursor highlighting the character
|
|
335
|
+
if (cursorCol < line.length) {
|
|
336
|
+
const beforeCursor = line.slice(0, cursorCol);
|
|
337
|
+
const cursorChar = line[cursorCol];
|
|
338
|
+
const afterCursor = line.slice(cursorCol + 1);
|
|
339
|
+
lines.push(` ${beforeCursor}${s.inverse(cursorChar)}${afterCursor}`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Cursor at end of line - show block cursor
|
|
343
|
+
lines.push(` ${line}${s.inverse(' ')}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
lines.push(` ${line}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Pad to fixed height for consistency
|
|
351
|
+
for (let i = endLine - startLine; i < this.maxVisibleLines; i++) {
|
|
352
|
+
lines.push('');
|
|
353
|
+
}
|
|
354
|
+
// Show scroll indicator if needed
|
|
355
|
+
if (wrappedLines.length > this.maxVisibleLines) {
|
|
356
|
+
const scrollInfo = `Lines ${String(startLine + 1)}-${String(endLine)} of ${String(wrappedLines.length)}`;
|
|
357
|
+
lines.push(s.muted(` ${scrollInfo}`));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
lines.push('');
|
|
361
|
+
}
|
|
362
|
+
const remaining = MAX_ANCHOR_LENGTH - this.editState.editContent.length;
|
|
363
|
+
lines.push(s.muted(` ${String(remaining)} chars remaining`));
|
|
364
|
+
// Message
|
|
365
|
+
if (this.editState.message) {
|
|
366
|
+
lines.push('');
|
|
367
|
+
switch (this.editState.messageType) {
|
|
368
|
+
case 'success':
|
|
369
|
+
lines.push(s.success(` ${this.editState.message}`));
|
|
370
|
+
break;
|
|
371
|
+
case 'error':
|
|
372
|
+
lines.push(s.error(` ${this.editState.message}`));
|
|
373
|
+
break;
|
|
374
|
+
default:
|
|
375
|
+
lines.push(s.muted(` ${this.editState.message}`));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
lines.push('');
|
|
379
|
+
lines.push(border);
|
|
380
|
+
lines.push(s.muted(' ←→ Navigate · Opt+←/→ Word jump · Enter Save · Esc Cancel'));
|
|
381
|
+
lines.push(border);
|
|
382
|
+
return lines;
|
|
383
|
+
}
|
|
384
|
+
handleKey(data) {
|
|
385
|
+
const char = extractPrintable(data);
|
|
386
|
+
const keyStr = data.toString();
|
|
387
|
+
const item = this.editState.targetItem;
|
|
388
|
+
if (!item) {
|
|
389
|
+
return popScreen();
|
|
390
|
+
}
|
|
391
|
+
if (isCtrlC(data)) {
|
|
392
|
+
return closeOverlay(undefined);
|
|
393
|
+
}
|
|
394
|
+
if (isEscape(data)) {
|
|
395
|
+
this.editState.message = null;
|
|
396
|
+
this.editState.targetItem = null;
|
|
397
|
+
return popScreen();
|
|
398
|
+
}
|
|
399
|
+
// Enter - save (since we use word-wrap, Enter saves instead of inserting newline)
|
|
400
|
+
if (isEnter(data)) {
|
|
401
|
+
return this.saveAndClose();
|
|
402
|
+
}
|
|
403
|
+
// Ctrl+S also saves
|
|
404
|
+
if (keyStr === '\x13') {
|
|
405
|
+
return this.saveAndClose();
|
|
406
|
+
}
|
|
407
|
+
// Left arrow - cursor left
|
|
408
|
+
if (isLeftArrow(data)) {
|
|
409
|
+
if (this.editState.editCursor > 0) {
|
|
410
|
+
this.editState.editCursor--;
|
|
411
|
+
return stay();
|
|
412
|
+
}
|
|
413
|
+
return stay(false);
|
|
414
|
+
}
|
|
415
|
+
// Right arrow - cursor right
|
|
416
|
+
if (isRightArrow(data)) {
|
|
417
|
+
if (this.editState.editCursor < this.editState.editContent.length) {
|
|
418
|
+
this.editState.editCursor++;
|
|
419
|
+
return stay();
|
|
420
|
+
}
|
|
421
|
+
return stay(false);
|
|
422
|
+
}
|
|
423
|
+
// Option+Left (ESC+b) - move to start of previous word
|
|
424
|
+
if (keyStr === '\x1bb') {
|
|
425
|
+
this.editState.editCursor = findPrevWordBoundary(this.editState.editContent, this.editState.editCursor);
|
|
426
|
+
return stay();
|
|
427
|
+
}
|
|
428
|
+
// Option+Right (ESC+f) - move to end of next word
|
|
429
|
+
if (keyStr === '\x1bf') {
|
|
430
|
+
this.editState.editCursor = findNextWordBoundary(this.editState.editContent, this.editState.editCursor);
|
|
431
|
+
return stay();
|
|
432
|
+
}
|
|
433
|
+
// Home/Ctrl+A - go to start
|
|
434
|
+
if (keyStr === '\x1b[H' || keyStr === '\x01') {
|
|
435
|
+
this.editState.editCursor = 0;
|
|
436
|
+
return stay();
|
|
437
|
+
}
|
|
438
|
+
// End/Ctrl+E - go to end
|
|
439
|
+
if (keyStr === '\x1b[F' || keyStr === '\x05') {
|
|
440
|
+
this.editState.editCursor = this.editState.editContent.length;
|
|
441
|
+
return stay();
|
|
442
|
+
}
|
|
443
|
+
// Backspace
|
|
444
|
+
if (isBackspace(data) && this.editState.editCursor > 0) {
|
|
445
|
+
this.editState.editContent =
|
|
446
|
+
this.editState.editContent.slice(0, this.editState.editCursor - 1) +
|
|
447
|
+
this.editState.editContent.slice(this.editState.editCursor);
|
|
448
|
+
this.editState.editCursor--;
|
|
449
|
+
return stay();
|
|
450
|
+
}
|
|
451
|
+
// Delete key
|
|
452
|
+
if (keyStr === '\x1b[3~' && this.editState.editCursor < this.editState.editContent.length) {
|
|
453
|
+
this.editState.editContent =
|
|
454
|
+
this.editState.editContent.slice(0, this.editState.editCursor) +
|
|
455
|
+
this.editState.editContent.slice(this.editState.editCursor + 1);
|
|
456
|
+
return stay();
|
|
457
|
+
}
|
|
458
|
+
// Printable character
|
|
459
|
+
if (char && this.editState.editContent.length < MAX_ANCHOR_LENGTH) {
|
|
460
|
+
this.editState.editContent =
|
|
461
|
+
this.editState.editContent.slice(0, this.editState.editCursor) +
|
|
462
|
+
char +
|
|
463
|
+
this.editState.editContent.slice(this.editState.editCursor);
|
|
464
|
+
this.editState.editCursor++;
|
|
465
|
+
return stay();
|
|
466
|
+
}
|
|
467
|
+
return stay(false);
|
|
468
|
+
}
|
|
469
|
+
saveAndClose() {
|
|
470
|
+
const item = this.editState.targetItem;
|
|
471
|
+
if (!item)
|
|
472
|
+
return popScreen();
|
|
473
|
+
if (this.editState.editContent.trim()) {
|
|
474
|
+
try {
|
|
475
|
+
const manager = item.scope === 'global'
|
|
476
|
+
? getGlobalAnchorManager()
|
|
477
|
+
: getAnchorManager(item.projectId ?? '');
|
|
478
|
+
const oldAnchor = item.anchor;
|
|
479
|
+
manager.remove(oldAnchor.id);
|
|
480
|
+
manager.add({
|
|
481
|
+
id: oldAnchor.id,
|
|
482
|
+
content: this.editState.editContent.trim(),
|
|
483
|
+
priority: oldAnchor.priority,
|
|
484
|
+
scope: oldAnchor.scope,
|
|
485
|
+
tags: oldAnchor.tags,
|
|
486
|
+
metadata: oldAnchor.metadata,
|
|
487
|
+
projectId: oldAnchor.projectId,
|
|
488
|
+
});
|
|
489
|
+
this.onComplete();
|
|
490
|
+
this.editState.message = null;
|
|
491
|
+
this.editState.targetItem = null;
|
|
492
|
+
return popScreen();
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
this.editState.message = `Error: ${error.message}`;
|
|
496
|
+
this.editState.messageType = 'error';
|
|
497
|
+
return stay();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
this.editState.message = 'Content cannot be empty';
|
|
502
|
+
this.editState.messageType = 'error';
|
|
503
|
+
return stay();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// =============================================================================
|
|
508
|
+
// Delete Confirm Screen
|
|
509
|
+
// =============================================================================
|
|
510
|
+
class DeleteConfirmScreen extends BaseScreen {
|
|
511
|
+
editState;
|
|
512
|
+
styles;
|
|
513
|
+
onComplete;
|
|
514
|
+
constructor(editState, styles, onComplete) {
|
|
515
|
+
super();
|
|
516
|
+
this.editState = editState;
|
|
517
|
+
this.styles = styles;
|
|
518
|
+
this.onComplete = onComplete;
|
|
519
|
+
}
|
|
520
|
+
render() {
|
|
521
|
+
const s = this.styles;
|
|
522
|
+
const cols = terminal.getTerminalWidth();
|
|
523
|
+
const border = renderBorder(cols, s);
|
|
524
|
+
const lines = [];
|
|
525
|
+
const item = this.editState.targetItem;
|
|
526
|
+
if (!item) {
|
|
527
|
+
lines.push(border);
|
|
528
|
+
lines.push(s.error(' No anchor selected'));
|
|
529
|
+
lines.push(border);
|
|
530
|
+
return lines;
|
|
531
|
+
}
|
|
532
|
+
lines.push(border);
|
|
533
|
+
lines.push(` ${s.warning('DELETE ANCHOR')}`);
|
|
534
|
+
lines.push('');
|
|
535
|
+
lines.push(s.muted(` ID: ${item.anchor.id}`));
|
|
536
|
+
lines.push(s.muted(` Priority: ${item.anchor.priority}`));
|
|
537
|
+
lines.push(s.muted(` Scope: ${item.scope}`));
|
|
538
|
+
lines.push('');
|
|
539
|
+
const maxContentLen = cols - 14;
|
|
540
|
+
let content = item.anchor.content;
|
|
541
|
+
if (content.length > maxContentLen) {
|
|
542
|
+
content = content.slice(0, maxContentLen - 3) + '...';
|
|
543
|
+
}
|
|
544
|
+
lines.push(` Content: ${content}`);
|
|
545
|
+
lines.push('');
|
|
546
|
+
lines.push(s.warning(' Are you sure you want to delete this anchor?'));
|
|
547
|
+
lines.push('');
|
|
548
|
+
lines.push(border);
|
|
549
|
+
lines.push(s.muted(' y Yes · n/Esc No'));
|
|
550
|
+
lines.push(border);
|
|
551
|
+
return lines;
|
|
552
|
+
}
|
|
553
|
+
handleKey(data) {
|
|
554
|
+
const char = extractPrintable(data);
|
|
555
|
+
const item = this.editState.targetItem;
|
|
556
|
+
if (!item) {
|
|
557
|
+
return popScreen();
|
|
558
|
+
}
|
|
559
|
+
if (isCtrlC(data)) {
|
|
560
|
+
return closeOverlay(undefined);
|
|
561
|
+
}
|
|
562
|
+
if (char === 'y' || char === 'Y') {
|
|
563
|
+
try {
|
|
564
|
+
const manager = item.scope === 'global'
|
|
565
|
+
? getGlobalAnchorManager()
|
|
566
|
+
: getAnchorManager(item.projectId ?? '');
|
|
567
|
+
manager.remove(item.anchor.id);
|
|
568
|
+
this.onComplete();
|
|
569
|
+
this.editState.targetItem = null;
|
|
570
|
+
return popScreen();
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
this.editState.message = `Error: ${error.message}`;
|
|
574
|
+
this.editState.messageType = 'error';
|
|
575
|
+
return popScreen();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (char === 'n' || char === 'N' || isEscape(data)) {
|
|
579
|
+
this.editState.targetItem = null;
|
|
580
|
+
return popScreen();
|
|
581
|
+
}
|
|
582
|
+
return stay(false);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// =============================================================================
|
|
586
|
+
// Anchors Overlay
|
|
587
|
+
// =============================================================================
|
|
588
|
+
class AnchorsOverlay extends TabbedListOverlay {
|
|
589
|
+
editState;
|
|
590
|
+
activeProject;
|
|
591
|
+
constructor() {
|
|
592
|
+
const activeProject = getActiveProject();
|
|
593
|
+
// Load anchors
|
|
594
|
+
const anchors = loadAnchors(activeProject?.id ?? null);
|
|
595
|
+
const config = {
|
|
596
|
+
title: 'Anchors',
|
|
597
|
+
tabs: TABS,
|
|
598
|
+
items: anchors,
|
|
599
|
+
pageSize: PAGE_SIZE,
|
|
600
|
+
filterByTab: (item, tabId) => {
|
|
601
|
+
if (tabId === 'all')
|
|
602
|
+
return true;
|
|
603
|
+
if (tabId === 'critical' || tabId === 'safety' || tabId === 'info') {
|
|
604
|
+
return item.anchor.priority === tabId;
|
|
605
|
+
}
|
|
606
|
+
if (tabId === 'global')
|
|
607
|
+
return item.scope === 'global';
|
|
608
|
+
if (tabId === 'project')
|
|
609
|
+
return item.scope === 'project';
|
|
610
|
+
return true;
|
|
611
|
+
},
|
|
612
|
+
getSearchText: (item) => `${item.anchor.content} ${item.anchor.priority} ${item.scope}`,
|
|
613
|
+
renderItem: (item, isSelected, s) => {
|
|
614
|
+
const cols = terminal.getTerminalWidth();
|
|
615
|
+
const priorityBadge = getPriorityBadge(item.anchor.priority, s);
|
|
616
|
+
const scopeBadge = item.scope === 'global' ? s.muted('[G]') : s.info('[P]');
|
|
617
|
+
const prefixLen = 15;
|
|
618
|
+
const maxContentLen = Math.max(20, cols - prefixLen - 8);
|
|
619
|
+
let content = item.anchor.content;
|
|
620
|
+
if (content.length > maxContentLen) {
|
|
621
|
+
content = content.slice(0, maxContentLen - 3) + '...';
|
|
622
|
+
}
|
|
623
|
+
const cursor = isSelected ? s.primary('❯ ') : ' ';
|
|
624
|
+
const line = `${priorityBadge} ${scopeBadge} ${content}`;
|
|
625
|
+
if (isSelected) {
|
|
626
|
+
return `${cursor}${s.primary(line)}`;
|
|
627
|
+
}
|
|
628
|
+
return `${cursor}${line}`;
|
|
629
|
+
},
|
|
630
|
+
showCount: true,
|
|
631
|
+
emptyMessage: 'No anchors defined. Press [a] to add one.',
|
|
632
|
+
noResultsMessage: 'No anchors match the filter.',
|
|
633
|
+
footerHints: (searchMode) => {
|
|
634
|
+
if (searchMode) {
|
|
635
|
+
return 'Type to filter · ↑↓/jk Navigate · Enter Edit · Esc Exit search';
|
|
636
|
+
}
|
|
637
|
+
return '↑↓/jk Navigate · ←→/hl Tabs · / Search · a Add · e Edit · d Delete · q/Esc Close';
|
|
638
|
+
},
|
|
639
|
+
// Render edit screens in-place, not full-screen
|
|
640
|
+
detailScreensFullScreen: false,
|
|
641
|
+
};
|
|
642
|
+
super(config);
|
|
643
|
+
this.activeProject = activeProject ? { id: activeProject.id, displayName: activeProject.displayName } : null;
|
|
644
|
+
this.editState = {
|
|
645
|
+
addContent: '',
|
|
646
|
+
addPriority: 'info',
|
|
647
|
+
addScope: activeProject ? 'project' : 'global',
|
|
648
|
+
addField: 'content',
|
|
649
|
+
editContent: '',
|
|
650
|
+
editCursor: 0,
|
|
651
|
+
targetItem: null,
|
|
652
|
+
message: null,
|
|
653
|
+
messageType: 'info',
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Refresh anchors from storage
|
|
658
|
+
*/
|
|
659
|
+
refreshAnchors() {
|
|
660
|
+
const anchors = loadAnchors(this.activeProject?.id ?? null);
|
|
661
|
+
this.state.items = anchors;
|
|
662
|
+
// Re-apply tab filter
|
|
663
|
+
const tabId = TABS[this.state.currentTab]?.id ?? 'all';
|
|
664
|
+
let filtered = anchors.filter((item) => {
|
|
665
|
+
if (tabId === 'all')
|
|
666
|
+
return true;
|
|
667
|
+
if (tabId === 'critical' || tabId === 'safety' || tabId === 'info') {
|
|
668
|
+
return item.anchor.priority === tabId;
|
|
669
|
+
}
|
|
670
|
+
if (tabId === 'global')
|
|
671
|
+
return item.scope === 'global';
|
|
672
|
+
if (tabId === 'project')
|
|
673
|
+
return item.scope === 'project';
|
|
674
|
+
return true;
|
|
675
|
+
});
|
|
676
|
+
// Re-apply search filter
|
|
677
|
+
if (this.state.searchQuery) {
|
|
678
|
+
const query = this.state.searchQuery.toLowerCase();
|
|
679
|
+
filtered = filtered.filter((item) => item.anchor.content.toLowerCase().includes(query) ||
|
|
680
|
+
item.anchor.priority.toLowerCase().includes(query) ||
|
|
681
|
+
item.scope.toLowerCase().includes(query));
|
|
682
|
+
}
|
|
683
|
+
this.state.filteredItems = filtered;
|
|
684
|
+
this.state.selectedIndex = Math.min(this.state.selectedIndex, Math.max(0, filtered.length - 1));
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Override to intercept custom keys
|
|
688
|
+
*/
|
|
689
|
+
handleKey(data) {
|
|
690
|
+
const char = extractPrintable(data);
|
|
691
|
+
// Only intercept when on the main list screen (screen stack size = 1)
|
|
692
|
+
if (this.screenStack.size() === 1) {
|
|
693
|
+
// Ctrl+C always closes
|
|
694
|
+
if (isCtrlC(data)) {
|
|
695
|
+
this.close();
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
// Don't intercept if in search mode
|
|
699
|
+
if (!this.state.searchMode) {
|
|
700
|
+
// q/Esc closes
|
|
701
|
+
if (isClose(data)) {
|
|
702
|
+
this.close();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
// 'a' - add mode
|
|
706
|
+
if (char === 'a') {
|
|
707
|
+
this.editState.addContent = '';
|
|
708
|
+
this.editState.addPriority = 'info';
|
|
709
|
+
this.editState.addScope = this.activeProject ? 'project' : 'global';
|
|
710
|
+
this.editState.addField = 'content';
|
|
711
|
+
this.editState.message = null;
|
|
712
|
+
this.screenStack.push(new AddAnchorScreen(this.editState, this.styles, this.activeProject !== null, this.activeProject?.id ?? null, () => { this.refreshAnchors(); }));
|
|
713
|
+
this.update();
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
// 'e' or Enter - edit mode
|
|
717
|
+
if ((char === 'e' || isEnter(data)) && this.state.filteredItems.length > 0) {
|
|
718
|
+
const item = this.state.filteredItems[this.state.selectedIndex];
|
|
719
|
+
this.editState.targetItem = item;
|
|
720
|
+
this.editState.editContent = item.anchor.content;
|
|
721
|
+
this.editState.editCursor = item.anchor.content.length;
|
|
722
|
+
this.editState.message = null;
|
|
723
|
+
this.screenStack.push(new EditAnchorScreen(this.editState, this.styles, () => { this.refreshAnchors(); }));
|
|
724
|
+
this.update();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
// 'd' or backspace - delete confirm
|
|
728
|
+
if ((char === 'd' || isBackspace(data)) && this.state.filteredItems.length > 0) {
|
|
729
|
+
const item = this.state.filteredItems[this.state.selectedIndex];
|
|
730
|
+
this.editState.targetItem = item;
|
|
731
|
+
this.editState.message = null;
|
|
732
|
+
this.screenStack.push(new DeleteConfirmScreen(this.editState, this.styles, () => { this.refreshAnchors(); }));
|
|
733
|
+
this.update();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// Let TabbedListOverlay handle everything else
|
|
739
|
+
super.handleKey(data);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// =============================================================================
|
|
743
|
+
// Helper: Load Anchors
|
|
744
|
+
// =============================================================================
|
|
745
|
+
function loadAnchors(activeProjectId) {
|
|
746
|
+
const items = [];
|
|
747
|
+
// Global anchors
|
|
748
|
+
const globalManager = getGlobalAnchorManager();
|
|
749
|
+
for (const anchor of globalManager.getAll()) {
|
|
750
|
+
items.push({ anchor, scope: 'global', projectId: null });
|
|
751
|
+
}
|
|
752
|
+
// Project anchors (if active project)
|
|
753
|
+
if (activeProjectId !== null) {
|
|
754
|
+
const projectManager = getAnchorManager(String(activeProjectId));
|
|
755
|
+
for (const anchor of projectManager.getAll()) {
|
|
756
|
+
items.push({ anchor, scope: 'project', projectId: String(activeProjectId) });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Sort by priority (critical first), then by content
|
|
760
|
+
const priorityOrder = { critical: 0, safety: 1, info: 2 };
|
|
761
|
+
items.sort((a, b) => {
|
|
762
|
+
const pa = priorityOrder[a.anchor.priority] ?? 2;
|
|
763
|
+
const pb = priorityOrder[b.anchor.priority] ?? 2;
|
|
764
|
+
if (pa !== pb)
|
|
765
|
+
return pa - pb;
|
|
766
|
+
return a.anchor.content.localeCompare(b.anchor.content);
|
|
767
|
+
});
|
|
768
|
+
return items;
|
|
769
|
+
}
|
|
770
|
+
// =============================================================================
|
|
771
|
+
// Export Function (Backward Compatible)
|
|
772
|
+
// =============================================================================
|
|
773
|
+
export function showAnchorsOverlay() {
|
|
774
|
+
return new AnchorsOverlay().show();
|
|
775
|
+
}
|