Skip to content

Commit eaa6887

Browse files
authored
fix: preserve target registry across workspace loads
Preserve target directory registry entries while loading workspace state and legacy states. - Merge workspace-local target directories with the wider registry - Preserve aliases across workspace deletion and setup - Report setup session failures instead of silently completing - Add regression coverage for registry preservation
1 parent 066f7e5 commit eaa6887

4 files changed

Lines changed: 155 additions & 14 deletions

File tree

enkan-repl.el

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ ALIASES may be a list of strings or an alist of (alias . project-name)."
411411
(or (cdr (assoc alias project-aliases))
412412
alias))
413413

414+
(defun enkan-repl--merge-target-directories (primary fallback)
415+
"Return target directories from PRIMARY merged with FALLBACK.
416+
Entries in PRIMARY win when both lists contain the same alias. This keeps
417+
workspace-local imported paths authoritative while preserving the user's wider
418+
target directory registry across workspace loads."
419+
(append primary
420+
(cl-remove-if (lambda (entry)
421+
(assoc (car entry) primary))
422+
fallback)))
423+
414424
(defun enkan-repl--projects-with-current-aliases (projects current-project project-aliases)
415425
"Return PROJECTS augmented with CURRENT-PROJECT's PROJECT-ALIASES.
416426
Merge the current workspace's aliases into CURRENT-PROJECT's project
@@ -460,7 +470,9 @@ This function restores workspace state from the given plist."
460470
(setq enkan-repl-project-aliases (plist-get state :project-aliases)))
461471
(when (plist-member state :target-directories)
462472
(setq enkan-repl-target-directories
463-
(plist-get state :target-directories))))
473+
(enkan-repl--merge-target-directories
474+
(plist-get state :target-directories)
475+
enkan-repl-target-directories))))
464476

465477
(defun enkan-repl--save-workspace-state (&optional workspace-id)
466478
"Save current globals into `enkan-repl--workspaces' under WORKSPACE-ID.
@@ -798,23 +810,29 @@ This ensures that directory lookups work correctly after workspace switch."
798810
;; Find project directories from files in standard locations
799811
(let ((dirs '()))
800812
;; Check standard project locations
801-
(dolist (alias enkan-repl-project-aliases)
813+
(dolist (alias (enkan-repl--project-alias-names enkan-repl-project-aliases))
802814
;; Try to find project directory from standard locations
803-
(let* ((home-dir (expand-file-name "~"))
815+
(let* ((project-name
816+
(enkan-repl--project-name-for-alias
817+
alias enkan-repl-project-aliases))
818+
(home-dir (expand-file-name "~"))
804819
(common-paths (list
805-
(expand-file-name (format "dev/self/%s" enkan-repl--current-project) home-dir)
806-
(expand-file-name (format "dev/%s" enkan-repl--current-project) home-dir)
807-
(expand-file-name (format "Documents/%s" enkan-repl--current-project) home-dir)
808-
(expand-file-name (format "projects/%s" enkan-repl--current-project) home-dir)
809-
(expand-file-name enkan-repl--current-project default-directory))))
820+
(expand-file-name (format "dev/self/%s" project-name) home-dir)
821+
(expand-file-name (format "dev/%s" project-name) home-dir)
822+
(expand-file-name (format "Documents/%s" project-name) home-dir)
823+
(expand-file-name (format "projects/%s" project-name) home-dir)
824+
(expand-file-name project-name default-directory))))
810825
;; Find first existing directory
811826
(dolist (path common-paths)
812827
(when (and (not (assoc alias dirs))
813828
(file-directory-p path))
814-
(push (cons alias (cons enkan-repl--current-project path)) dirs)))))
829+
(push (cons alias (cons project-name path)) dirs)))))
815830
;; Update enkan-repl-target-directories if we found directories
816831
(when dirs
817-
(setq enkan-repl-target-directories dirs)))))
832+
(setq enkan-repl-target-directories
833+
(enkan-repl--merge-target-directories
834+
(nreverse dirs)
835+
enkan-repl-target-directories))))))
818836

819837
(defun enkan-repl--initialize-default-workspace ()
820838
"Initialize default workspace '01' with first available project.
@@ -1509,7 +1527,8 @@ COUNTER: session counter"
15091527

15101528
(defun enkan-repl--setup-start-sessions (alias-list buffer-name)
15111529
"Start terminal sessions for each alias in ALIAS-LIST and log to BUFFER-NAME.
1512-
Includes error handling for individual session failures."
1530+
Includes error handling for individual session failures.
1531+
Returns a plist with `:success-count' and `:failure-count'."
15131532
(with-current-buffer buffer-name
15141533
(princ "🚀 Starting terminal sessions:\n"))
15151534
(let ((session-number 1)
@@ -1533,7 +1552,9 @@ Includes error handling for individual session failures."
15331552
(setq failure-count (1+ failure-count))))
15341553
(setq session-number (1+ session-number)))
15351554
(with-current-buffer buffer-name
1536-
(princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count)))))
1555+
(princ (format "\n📊 Session start summary: %d success, %d failed\n\n" success-count failure-count)))
1556+
(list :success-count success-count
1557+
:failure-count failure-count)))
15371558

15381559
(defun enkan-repl--setup-project-session (alias)
15391560
"Setup project session for given ALIAS.
@@ -1600,7 +1621,12 @@ Category: Session Controller"
16001621
(error "Project '%s' not found" project-name))
16011622
(enkan-repl--setup-set-project-aliases project-name alias-list buffer-name)
16021623
;; Start sessions
1603-
(enkan-repl--setup-start-sessions alias-list buffer-name))
1624+
(let ((start-result
1625+
(enkan-repl--setup-start-sessions alias-list buffer-name)))
1626+
(when (> (plist-get start-result :failure-count) 0)
1627+
(error "Failed to start %d of %d terminal session(s)"
1628+
(plist-get start-result :failure-count)
1629+
(length alias-list)))))
16041630
;; Set final project configuration
16051631
(enkan-repl--ws-set-current-project project-name)
16061632
(princ (format "\n✅ Setup completed for project: %s\n" project-name))

test/enkan-repl-workspace-create-test.el

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@
118118
(should (string= (plist-get ws-state :current-project) "MyProject"))
119119
(should (equal (plist-get ws-state :project-aliases) '("alias1"))))))
120120

121+
(ert-deftest test-enkan-repl--setup-start-sessions-reports-failures ()
122+
"Session setup should report failures instead of looking successful."
123+
(let ((buffer-name "*enkan-repl-test-setup-start*")
124+
(enkan-repl-target-directories nil))
125+
(unwind-protect
126+
(with-current-buffer (get-buffer-create buffer-name)
127+
(erase-buffer)
128+
(let* ((standard-output (current-buffer))
129+
(result (enkan-repl--setup-start-sessions
130+
'("junk")
131+
buffer-name)))
132+
(should (= 0 (plist-get result :success-count)))
133+
(should (= 1 (plist-get result :failure-count)))
134+
(should (string-match-p "Project alias .junk. not found"
135+
(buffer-string)))))
136+
(when (get-buffer buffer-name)
137+
(kill-buffer buffer-name)))))
138+
121139
(ert-deftest test-enkan-repl--setup-window-terminal-buffer-pure-workspace-context ()
122140
"Test that window eat buffer name includes correct workspace ID."
123141
;; Load window-layouts if exists
@@ -149,4 +167,4 @@
149167
(should (string-match-p "\\*ws:02 " (cdr result))))))))
150168

151169
(provide 'enkan-repl-workspace-create-test)
152-
;;; enkan-repl-workspace-create-test.el ends here
170+
;;; enkan-repl-workspace-create-test.el ends here

test/enkan-repl-workspace-delete-deletion-test.el

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,39 @@
4444
(should (equal 1 enkan-repl--session-counter))
4545
(should (equal "other" enkan-repl--current-project)))))
4646

47+
(ert-deftest test-workspace-delete-preserves-target-registry-for-new-setup ()
48+
"Deleting a workspace must not make unrelated project aliases unusable."
49+
(let ((enkan-repl--workspaces
50+
'(("01" . (:current-project "junk"
51+
:session-list ((1 . "junk"))
52+
:session-counter 1
53+
:project-aliases (("junk" . "junk"))
54+
:target-directories
55+
(("junk" . ("junk" . "/repo/junk")))))
56+
("02" . (:current-project "er"
57+
:session-list ((1 . "enkan-repl"))
58+
:session-counter 1
59+
:project-aliases (("er" . "enkan-repl"))
60+
:target-directories
61+
(("er" . ("enkan-repl" . "/repo/enkan-repl")))))))
62+
(enkan-repl--current-workspace "01")
63+
(enkan-repl-session-list '((1 . "junk")))
64+
(enkan-repl--session-counter 1)
65+
(enkan-repl--current-project "junk")
66+
(enkan-repl-project-aliases '(("junk" . "junk")))
67+
(enkan-repl-target-directories
68+
'(("junk" . ("junk" . "/repo/junk"))
69+
("er" . ("enkan-repl" . "/repo/enkan-repl")))))
70+
(cl-letf (((symbol-function 'enkan-repl--stop-workspace-terminals)
71+
(lambda (_workspace-id)
72+
(list :buffers-killed 0 :tmux-killed nil))))
73+
(enkan-repl--delete-workspace-completely "01")
74+
(should (equal "02" enkan-repl--current-workspace))
75+
(should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl"))
76+
("junk" . ("junk" . "/repo/junk")))
77+
enkan-repl-target-directories))
78+
(should (equal '("junk" . "/repo/junk")
79+
(enkan-repl--setup-project-session "junk"))))))
80+
4781
(provide 'enkan-repl-workspace-delete-deletion-test)
4882
;;; enkan-repl-workspace-delete-deletion-test.el ends here

test/enkan-repl-workspace-state-test.el

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,69 @@
7373
(should (equal enkan-repl--session-counter 2))
7474
(should (equal enkan-repl-project-aliases '(("p3" . "proj3") ("p4" . "proj4")))))))
7575

76+
(ert-deftest test-enkan-repl--load-workspace-state-preserves-target-registry ()
77+
"Loading a workspace should not discard unrelated configured targets."
78+
(let ((enkan-repl--workspaces
79+
'(("01" . (:current-project "er"
80+
:session-list ((1 . "enkan-repl"))
81+
:session-counter 1
82+
:project-aliases (("er" . "enkan-repl"))
83+
:target-directories
84+
(("er" . ("enkan-repl" . "/repo/enkan-repl")))))))
85+
(enkan-repl--current-workspace "01")
86+
(enkan-repl--current-project nil)
87+
(enkan-repl-session-list nil)
88+
(enkan-repl--session-counter 0)
89+
(enkan-repl-project-aliases nil)
90+
(enkan-repl-target-directories
91+
'(("er" . ("enkan-repl" . "/repo/enkan-repl"))
92+
("junk" . ("junk" . "/repo/junk")))))
93+
(enkan-repl--load-workspace-state "01")
94+
(should (equal '(("er" . ("enkan-repl" . "/repo/enkan-repl"))
95+
("junk" . ("junk" . "/repo/junk")))
96+
enkan-repl-target-directories))
97+
(should (equal '("junk" . "/repo/junk")
98+
(enkan-repl--setup-project-session "junk")))))
99+
100+
(ert-deftest test-enkan-repl--load-legacy-state-merges-discovered-targets ()
101+
"Loading old states without target dirs should preserve the target registry."
102+
(let* ((project-name "enkan-repl-legacy-project")
103+
(temp-root (file-name-as-directory
104+
(make-temp-file "enkan-repl-legacy-state-" t)))
105+
(project-dir (expand-file-name project-name temp-root))
106+
(default-directory temp-root)
107+
(enkan-repl--workspaces
108+
`(("01" . (:current-project ,project-name
109+
:session-list ((1 . ,project-name))
110+
:session-counter 1
111+
:project-aliases (("legacy" . ,project-name))))))
112+
(enkan-repl--current-workspace "01")
113+
(enkan-repl--current-project nil)
114+
(enkan-repl-session-list nil)
115+
(enkan-repl--session-counter 0)
116+
(enkan-repl-project-aliases nil)
117+
(enkan-repl-target-directories
118+
'(("junk" . ("junk" . "/repo/junk")))))
119+
(unwind-protect
120+
(progn
121+
(make-directory project-dir)
122+
(enkan-repl--load-workspace-state "01")
123+
(should (equal `(("legacy" . (,project-name . ,project-dir))
124+
("junk" . ("junk" . "/repo/junk")))
125+
enkan-repl-target-directories))
126+
(should (equal '("junk" . "/repo/junk")
127+
(enkan-repl--setup-project-session "junk"))))
128+
(delete-directory temp-root t))))
129+
130+
(ert-deftest test-enkan-repl--merge-target-directories-primary-wins ()
131+
"Workspace target directories should win over preserved registry entries."
132+
(should (equal '(("er" . ("enkan-repl" . "/state/enkan-repl"))
133+
("junk" . ("junk" . "/repo/junk")))
134+
(enkan-repl--merge-target-directories
135+
'(("er" . ("enkan-repl" . "/state/enkan-repl")))
136+
'(("er" . ("enkan-repl" . "/config/enkan-repl"))
137+
("junk" . ("junk" . "/repo/junk")))))))
138+
76139
(ert-deftest test-enkan-repl--save-workspace-state ()
77140
"Test saving workspace state."
78141
(let ((enkan-repl--workspaces nil)

0 commit comments

Comments
 (0)