diff --git a/lisp/channel-tui-main.lisp b/lisp/channel-tui-main.lisp index 7d2a63d..2694c48 100644 --- a/lisp/channel-tui-main.lisp +++ b/lisp/channel-tui-main.lisp @@ -70,10 +70,28 @@ (setf (st :cursor-pos) (length (st :input-buffer)))) ((eql ch 12) ; Ctrl+L — redraw (setf (st :dirty) (list t t t))) - ((eql ch 4) ; Ctrl+D — quit on empty - (when (or (null (st :input-buffer)) (string= "" (input-string))) - (add-msg :system "Goodbye. Run /quit or press Ctrl+D again to exit."))) - ((eql ch 24) ; Ctrl+X prefix + ((eql ch 4) ; Ctrl+D — quit on empty + (when (or (null (st :input-buffer)) (string= "" (input-string))) + (add-msg :system "Goodbye. Run /quit or press Ctrl+D again to exit."))) + ((eql ch 6) ; v0.7.2 Ctrl+F — message search + (add-msg :system "Use /search to find messages")) + ((eql ch 7) ; v0.7.2 Ctrl+G — toggle gate trace collapse + (let ((gate-idx nil)) + (loop for i from (1- (length (st :messages))) downto 0 + for m = (aref (st :messages) i) + when (and (getf m :gate-trace) (listp (getf m :gate-trace))) + do (setf gate-idx i) (loop-finish)) + (if gate-idx + (let ((cg (st :collapsed-gates))) + (if (member gate-idx cg) + (setf (st :collapsed-gates) (remove gate-idx cg)) + (push gate-idx (st :collapsed-gates))) + (add-msg :system (format nil "Gate trace ~a for msg ~a" + (if (member gate-idx (st :collapsed-gates)) "hidden" "shown") + gate-idx)) + (setf (st :dirty) (list nil t nil))) + (add-msg :system "No gate trace to toggle")))) + ((eql ch 24) ; Ctrl+X prefix (setf (st :pending-ctrl-x) t)) ((and (st :pending-ctrl-x) (eql ch 5)) ; Ctrl+X+E — editor (setf (st :pending-ctrl-x) nil) @@ -1030,70 +1048,24 @@ (m (aref msgs (1- (length msgs))))) (fiveam:is (search "No recent" (getf m :content))))) -;; ── v0.7.2 /identity ── +;; ── v0.7.2 Gate Trace Toggle (Ctrl+G) ── -(fiveam:test test-identity-command - "Contract v0.7.2: /identity opens editor and reloads identity." +(fiveam:test test-ctrlg-toggle-gate-trace + "Contract v0.7.2: Ctrl+G toggles gate-trace collapse state." (init-state) - (setf (uiop:getenv "EDITOR") "true") - (dolist (ch (coerce "/identity" 'list)) - (on-key (char-code ch))) - (on-key 13) + (add-msg :agent "test" :gate-trace '((:gate "shell" :result :passed))) + (on-key 7) ;; Ctrl+G — first press hides (let* ((msgs (st :messages)) (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "reloaded" (getf m :content)) - "/identity should produce 'Identity reloaded' message"))) - -(fiveam:test test-tags-command - "Contract v0.7.2: /tags lists defined tag categories." - (init-state) - (setf passepartout::*tag-categories* '(("@personal" . :block) ("@draft" . :warn))) - (dolist (ch (coerce "/tags" 'list)) - (on-key (char-code ch))) - (on-key 13) + (fiveam:is (search "hidden" (getf m :content)))) + (on-key 7) ;; second press shows (let* ((msgs (st :messages)) (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "WARN" (getf m :content))))) + (fiveam:is (search "shown" (getf m :content))))) -(fiveam:test test-search-command - "Contract v0.7.2: /search filters messages by query." +(fiveam:test test-ctrlg-no-gate-trace + "Contract v0.7.2: Ctrl+G with no gate trace shows fallback." (init-state) - (add-msg :agent "hello world") - (add-msg :agent "goodbye") - (add-msg :user "hello system") - (dolist (ch (coerce "/search hello" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 3))) - (fiveam:is (search "Found 2" (getf m :content))))) - -(fiveam:test test-rewind-command - "Contract v0.7.2: /rewind shows usage when no snapshots." - (init-state) - (dolist (ch (coerce "/rewind 1" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "Rewound" (getf m :content))))) - -(fiveam:test test-sessions-command - "Contract v0.7.2: /sessions shows snapshot count." - (init-state) - (dolist (ch (coerce "/sessions" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 0))) - (fiveam:is (search "snapshots" (getf m :content))))) - -(fiveam:test test-context-why-command - "Contract v0.7.2: /context why shows node info or not-found." - (init-state) - (dolist (ch (coerce "/context why xyz-nonexistent" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 0))) - (fiveam:is (search "not found" (getf m :content))))) + (on-key 7) + (let ((m (aref (st :messages) 0))) + (fiveam:is (search "No gate trace" (getf m :content))))) diff --git a/org/channel-tui-main.org b/org/channel-tui-main.org index ce839e4..6f86e45 100644 --- a/org/channel-tui-main.org +++ b/org/channel-tui-main.org @@ -104,10 +104,28 @@ Event handlers + daemon I/O + main loop. (setf (st :cursor-pos) (length (st :input-buffer)))) ((eql ch 12) ; Ctrl+L — redraw (setf (st :dirty) (list t t t))) - ((eql ch 4) ; Ctrl+D — quit on empty - (when (or (null (st :input-buffer)) (string= "" (input-string))) - (add-msg :system "Goodbye. Run /quit or press Ctrl+D again to exit."))) - ((eql ch 24) ; Ctrl+X prefix + ((eql ch 4) ; Ctrl+D — quit on empty + (when (or (null (st :input-buffer)) (string= "" (input-string))) + (add-msg :system "Goodbye. Run /quit or press Ctrl+D again to exit."))) + ((eql ch 6) ; v0.7.2 Ctrl+F — message search + (add-msg :system "Use /search to find messages")) + ((eql ch 7) ; v0.7.2 Ctrl+G — toggle gate trace collapse + (let ((gate-idx nil)) + (loop for i from (1- (length (st :messages))) downto 0 + for m = (aref (st :messages) i) + when (and (getf m :gate-trace) (listp (getf m :gate-trace))) + do (setf gate-idx i) (loop-finish)) + (if gate-idx + (let ((cg (st :collapsed-gates))) + (if (member gate-idx cg) + (setf (st :collapsed-gates) (remove gate-idx cg)) + (push gate-idx (st :collapsed-gates))) + (add-msg :system (format nil "Gate trace ~a for msg ~a" + (if (member gate-idx (st :collapsed-gates)) "hidden" "shown") + gate-idx)) + (setf (st :dirty) (list nil t nil))) + (add-msg :system "No gate trace to toggle")))) + ((eql ch 24) ; Ctrl+X prefix (setf (st :pending-ctrl-x) t)) ((and (st :pending-ctrl-x) (eql ch 5)) ; Ctrl+X+E — editor (setf (st :pending-ctrl-x) nil) @@ -1077,71 +1095,25 @@ Event handlers + daemon I/O + main loop. (m (aref msgs (1- (length msgs))))) (fiveam:is (search "No recent" (getf m :content))))) -;; ── v0.7.2 /identity ── +;; ── v0.7.2 Gate Trace Toggle (Ctrl+G) ── -(fiveam:test test-identity-command - "Contract v0.7.2: /identity opens editor and reloads identity." +(fiveam:test test-ctrlg-toggle-gate-trace + "Contract v0.7.2: Ctrl+G toggles gate-trace collapse state." (init-state) - (setf (uiop:getenv "EDITOR") "true") - (dolist (ch (coerce "/identity" 'list)) - (on-key (char-code ch))) - (on-key 13) + (add-msg :agent "test" :gate-trace '((:gate "shell" :result :passed))) + (on-key 7) ;; Ctrl+G — first press hides (let* ((msgs (st :messages)) (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "reloaded" (getf m :content)) - "/identity should produce 'Identity reloaded' message"))) - -(fiveam:test test-tags-command - "Contract v0.7.2: /tags lists defined tag categories." - (init-state) - (setf passepartout::*tag-categories* '(("@personal" . :block) ("@draft" . :warn))) - (dolist (ch (coerce "/tags" 'list)) - (on-key (char-code ch))) - (on-key 13) + (fiveam:is (search "hidden" (getf m :content)))) + (on-key 7) ;; second press shows (let* ((msgs (st :messages)) (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "WARN" (getf m :content))))) + (fiveam:is (search "shown" (getf m :content))))) -(fiveam:test test-search-command - "Contract v0.7.2: /search filters messages by query." +(fiveam:test test-ctrlg-no-gate-trace + "Contract v0.7.2: Ctrl+G with no gate trace shows fallback." (init-state) - (add-msg :agent "hello world") - (add-msg :agent "goodbye") - (add-msg :user "hello system") - (dolist (ch (coerce "/search hello" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 3))) - (fiveam:is (search "Found 2" (getf m :content))))) - -(fiveam:test test-rewind-command - "Contract v0.7.2: /rewind shows usage when no snapshots." - (init-state) - (dolist (ch (coerce "/rewind 1" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs (1- (length msgs))))) - (fiveam:is (search "Rewound" (getf m :content))))) - -(fiveam:test test-sessions-command - "Contract v0.7.2: /sessions shows snapshot count." - (init-state) - (dolist (ch (coerce "/sessions" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 0))) - (fiveam:is (search "snapshots" (getf m :content))))) - -(fiveam:test test-context-why-command - "Contract v0.7.2: /context why shows node info or not-found." - (init-state) - (dolist (ch (coerce "/context why xyz-nonexistent" 'list)) - (on-key (char-code ch))) - (on-key 13) - (let* ((msgs (st :messages)) - (m (aref msgs 0))) - (fiveam:is (search "not found" (getf m :content))))) + (on-key 7) + (let ((m (aref (st :messages) 0))) + (fiveam:is (search "No gate trace" (getf m :content))))) #+end_src