aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-16 23:02:13 -0500
committerCraig Jennings <c@cjennings.net>2026-06-16 23:02:13 -0500
commit8061ec3d83fdd9896f84e4d8590eb878277e4949 (patch)
tree4eed2195cef717b87c7c9b3510442ee49b0af45d
parent0ab4afcd1e4e336cb69bd983e498bc441828ed5b (diff)
downloadpearl-8061ec3d83fdd9896f84e4d8590eb878277e4949.tar.gz
pearl-8061ec3d83fdd9896f84e4d8590eb878277e4949.zip
fix(views): accept Linear's open filter when copying a view down
The copy-down reverse-compiler only treated state.type nin [completed, canceled, duplicate] (Pearl's own :open predicate) as open. Linear's UI authors open as nin [completed, canceled], without duplicate, so copying down any standard open-issues view refused with "state.type uses a nin that isn't Pearl's open predicate" and never reached the save. A nin now maps to :open when every excluded type is a closed type and the set excludes both completed and canceled. That covers Linear's two-type form and Pearl's three-type form. A nin missing one of the two, or one that also excludes an open type like backlog, still refuses.
-rw-r--r--pearl.el13
-rw-r--r--tests/test-pearl-reverse-compile.el32
2 files changed, 41 insertions, 4 deletions
diff --git a/pearl.el b/pearl.el
index 41abab6..e4958e9 100644
--- a/pearl.el
+++ b/pearl.el
@@ -1137,10 +1137,17 @@ multi-value filter would change which issues match."
(let ((op (cdr (assq 'type node))))
(cond
((assq 'nin op)
+ ;; Linear's UI authors "open" as `state.type nin [completed,
+ ;; canceled]', omitting duplicate; Pearl's own `:open' adds it.
+ ;; Accept any nin whose excluded types are all closed types and that
+ ;; excludes both completed and canceled -- the canonical open filter,
+ ;; with or without duplicate. A nin missing one of the two, or one
+ ;; that also excludes an open type (e.g. backlog), is a different
+ ;; filter and still refuses.
(let ((vals (append (cdr (assq 'nin op)) nil)))
- (if (and (= (length vals) (length pearl--open-state-types))
- (cl-subsetp vals pearl--open-state-types :test #'string=)
- (cl-subsetp pearl--open-state-types vals :test #'string=))
+ (if (and (cl-subsetp vals pearl--open-state-types :test #'string=)
+ (member "completed" vals)
+ (member "canceled" vals))
(cons 'ok (list :open t))
(pearl--rc-refuse
"state.type uses a `nin' that isn't Pearl's open predicate"))))
diff --git a/tests/test-pearl-reverse-compile.el b/tests/test-pearl-reverse-compile.el
index 209d9a6..d298fb3 100644
--- a/tests/test-pearl-reverse-compile.el
+++ b/tests/test-pearl-reverse-compile.el
@@ -93,6 +93,24 @@
(ert-deftest test-pearl-rc-roundtrip-open-with-project ()
(test-pearl-rc--should-roundtrip '(:project "proj-9" :open t)))
+(ert-deftest test-pearl-rc-open-linear-canonical-two-type-nin ()
+ "Linear's UI authors open as state.type nin [completed, canceled] (no
+duplicate); it must reverse-compile to :open even though Pearl's own predicate
+adds duplicate."
+ (let ((r (test-pearl-rc--from-json
+ "{\"state\":{\"type\":{\"nin\":[\"completed\",\"canceled\"]}}}")))
+ (should (eq 'ok (car r)))
+ (should (equal (cdr r) '(:open t)))))
+
+(ert-deftest test-pearl-rc-open-linear-two-type-nin-with-project ()
+ "An open-issues-in-a-project Linear view (the copy-down that was refused) maps
+to :open + :project."
+ (let ((r (test-pearl-rc--from-json
+ "{\"state\":{\"type\":{\"nin\":[\"completed\",\"canceled\"]}},\"project\":{\"id\":{\"eq\":\"P9\"}}}")))
+ (should (eq 'ok (car r)))
+ (should (equal (plist-get (cdr r) :open) t))
+ (should (equal (plist-get (cdr r) :project) "P9"))))
+
(ert-deftest test-pearl-rc-roundtrip-project-team-cycle ()
(test-pearl-rc--should-roundtrip '(:project "P" :team "ENG" :cycle "C")))
@@ -180,10 +198,22 @@
(should (string-match-p "dueDate" (cdr r)))))
(ert-deftest test-pearl-rc-refuses-generic-nin ()
- "A state.type.nin that is not Pearl's exact open predicate refuses."
+ "A state.type.nin over an open type (started) is not the open predicate."
(should (eq 'refuse (car (test-pearl-rc--from-json
"{\"state\":{\"type\":{\"nin\":[\"started\"]}}}")))))
+(ert-deftest test-pearl-rc-refuses-nin-missing-canceled ()
+ "A nin excluding only completed still shows canceled issues, so it is not
+Pearl's open predicate and refuses rather than mapping to :open."
+ (should (eq 'refuse (car (test-pearl-rc--from-json
+ "{\"state\":{\"type\":{\"nin\":[\"completed\"]}}}")))))
+
+(ert-deftest test-pearl-rc-refuses-nin-excluding-open-type ()
+ "A nin that also excludes an open type (backlog) is a different filter, not
+open, and refuses."
+ (should (eq 'refuse (car (test-pearl-rc--from-json
+ "{\"state\":{\"type\":{\"nin\":[\"completed\",\"canceled\",\"backlog\"]}}}")))))
+
(ert-deftest test-pearl-rc-refuses-priority-out-of-range ()
(should (eq 'refuse (car (test-pearl-rc--from-json
"{\"priority\":{\"eq\":9}}")))))