aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.org34
-rw-r--r--pearl.el65
-rw-r--r--tests/test-integration-acceptance.el4
-rw-r--r--tests/test-pearl-commands.el10
-rw-r--r--tests/test-pearl-comment-editing.el4
-rw-r--r--tests/test-pearl-comments.el16
-rw-r--r--tests/test-pearl-compose.el10
-rw-r--r--tests/test-pearl-keymap.el50
-rw-r--r--tests/test-pearl-menu.el4
-rw-r--r--tests/test-pearl-save.el2
-rw-r--r--tests/test-pearl-smoke.el2
-rw-r--r--tests/test-pearl-sync-wrappers.el4
12 files changed, 105 insertions, 100 deletions
diff --git a/README.org b/README.org
index ad12b7d..c5b01b0 100644
--- a/README.org
+++ b/README.org
@@ -7,7 +7,7 @@
" /We like lists because we don't want to die./ "
— /Umberto Eco/
-Pearl (backronym: *Pearl Edits and Reflects Linear*) brings Linear issues into Emacs as a working Org file. Fetch your open issues, a project, a Linear Custom View, an ad-hoc filter, or a saved local query; Pearl renders each issue as an Org heading with the description and comments in the body and Linear metadata in a namespaced property drawer. Edit what you need in the buffer, then save the ticket; Pearl reconciles each changed field against the remote with a conflict check.
+Pearl (backronym: *Pearl Edits and Reflects Linear*) brings Linear issues into Emacs as a working Org file. Fetch your open issues, a project, a Linear Custom View, an ad-hoc filter, or a saved local query; Pearl renders each issue as an Org heading with the description and comments in the body and Linear metadata in a namespaced property drawer. Edit what you need in the buffer, then save the issue; Pearl reconciles each changed field against the remote with a conflict check.
** Features
:PROPERTIES:
@@ -48,7 +48,7 @@ Pearl is not on MELPA yet.
#+begin_src emacs-lisp
(use-package pearl
:vc (:url "https://git.cjennings.net/pearl.git" :rev :newest)
- :commands (pearl-menu pearl-list-issues pearl-run-view pearl-new-issue)
+ :commands (pearl-menu pearl-list-issues pearl-run-view pearl-create-issue)
:bind ("C-c L" . pearl-menu)
:custom
(pearl-org-file-path (expand-file-name "gtd/linear.org" org-directory))
@@ -76,7 +76,7 @@ In =config.el=:
#+begin_src emacs-lisp
(use-package! pearl
- :commands (pearl-menu pearl-list-issues pearl-new-issue
+ :commands (pearl-menu pearl-list-issues pearl-create-issue
pearl-run-view pearl-save-issue)
:init
(setq pearl-org-file-path (expand-file-name "gtd/linear.org" org-directory))
@@ -144,7 +144,7 @@ Pearl writes one active Org file. Running a different query or view replaces tha
*** Command menu
-=M-x pearl-menu= opens a transient dispatcher. Ticket actions are grouped as save, edit, new, and delete; workspace actions as fetch, view, and setup. Bind that command if you use Pearl regularly:
+=M-x pearl-menu= opens a transient dispatcher. Issue actions are grouped as save, edit, create, and delete; workspace actions as fetch, view, and setup. Bind that command if you use Pearl regularly:
#+begin_src emacs-lisp
(global-set-key (kbd "C-c L") #'pearl-menu)
@@ -158,22 +158,22 @@ Pearl is fully keyboard-drivable. =pearl-mode=, a minor mode, turns on automatic
(setq pearl-keymap-prefix "C-c l") ; or nil to bind nothing
#+end_src
-The hot-path commands sit one key under the prefix; the rest are grouped into category sub-maps. The common ones appear in both places, so =d= and =e d= both edit the description.
+The hot-path commands sit one key under the prefix; the rest are grouped into sub-maps, one per action verb. The common ones appear in both places, so =d= and =e d= both edit the description.
| Key | Command |
|-----------+----------------------------------------------------------------------|
| =l= | list my open issues |
| =g= | refresh the view |
| =r= | refresh the issue at point |
-| =s= / =S= | save the ticket at point / save every ticket in the file |
+| =s= / =S= | save the issue at point / save every issue in the file |
| =d= | edit the description |
-| =c= | add a comment |
| =m= | open the full transient menu |
| =f= ... | fetch: =o= open issues, =p= by project, =f= filter, =v= view, =q= saved query |
| =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment |
-| =n= ... | new: =t= ticket, =c= comment |
-| =k= ... | delete: =t= ticket, =c= comment |
-| =u= ... | url: =o= open issue in browser, =v= open view in Linear |
+| =c= ... | create: =t= issue, =c= comment |
+| =k= ... | delete: =t= issue, =c= comment |
+| =o= ... | open: =i= issue in browser, =v= view in Linear |
+| =y= ... | copy: =u= issue URL |
To reach the map outside a Pearl buffer, bind it globally as well:
@@ -207,17 +207,17 @@ Sorting is local and deterministic. Query filters are AND-only; use a Linear Cus
*** Editing issues
-Pearl has one write path. Edit a ticket however you like in the buffer, then save it -- Pearl diffs each field against what it last fetched and pushes only what changed. Nothing pushes the moment you pick a value, and there is no per-field "push" command to remember. All issue commands work from anywhere inside an issue subtree.
+Pearl has one write path. Edit an issue however you like in the buffer, then save it -- Pearl diffs each field against what it last fetched and pushes only what changed. Nothing pushes the moment you pick a value, and there is no per-field "push" command to remember. All issue commands work from anywhere inside an issue subtree.
| Command | What it does |
|-----------------------------------+-------------------------------------------------------|
-| =pearl-save-issue= | Save every changed field of the ticket at point |
-| =pearl-save-all= | Save every changed ticket in the file (confirms once) |
+| =pearl-save-issue= | Save every changed field of the issue at point |
+| =pearl-save-all= | Save every changed issue in the file (confirms once) |
| =pearl-edit-description= | Edit the description in a focused compose buffer |
| =pearl-edit-state= | Pick a workflow state (reaches any of the team's) |
| =pearl-edit-assignee= | Pick a team member as assignee |
| =pearl-edit-labels= | Pick labels; an empty selection clears them |
-| =pearl-add-comment= | Add a new Linear comment |
+| =pearl-create-comment= | Add a new Linear comment |
| =pearl-edit-current-comment= | Edit one of your own comments |
| =pearl-delete-current-comment= | Delete one of your own comments after confirming |
| =pearl-delete-current-issue= | Soft-delete the current issue after confirmation |
@@ -230,7 +230,7 @@ How each field is edited:
- *Priority* -- the Org priority cookie (=C-c ,=, or =S-up= / =S-down=). =[#A]/[#B]/[#C]/[#D]= are Urgent/High/Medium/Low; no cookie is None.
- *State* -- =pearl-edit-state=, completing over the team's workflow states.
- *Assignee and labels* -- =pearl-edit-assignee= / =pearl-edit-labels=, completing over the team's members and labels. Labels also render as Org tags on the heading (=:bug:backend:=), so you can filter, sparse-tree, and build agendas on them. The =:LINEAR-LABELS:= drawer stays the source of truth; hand-edited heading tags are ignored and rewritten from Linear on the next change or fetch, so change labels with =pearl-edit-labels=.
-- *Comments* -- edit your own in place, or =pearl-add-comment= for a new one.
+- *Comments* -- edit your own in place, or =pearl-create-comment= for a new one.
Picking a constrained field writes the value into the buffer and marks it changed; it doesn't push until you save. The display name or label text is there to read -- Pearl reconciles by the underlying id, and a refresh rewrites the display from the remote, so hand-editing the visible name has no effect.
@@ -242,9 +242,9 @@ Only comments you authored are editable or deletable. Pearl refuses edits and de
A fetched issue renders with a TODO keyword derived from its Linear state name: the name is slugified (upcased, non-alphanumeric runs collapsed to hyphens), so "In Progress" becomes =IN-PROGRESS= and "Dev Review" becomes =DEV-REVIEW=. The buffer's =#+TODO= line is built from the real workflow states of the teams on display, so every keyword you see is a state that team actually has, partitioned active / done by Linear's state type.
-You change a ticket's state two ways, both reconciled and pushed at the next save:
+You change an issue's state two ways, both reconciled and pushed at the next save:
-- Cycle the keyword with =C-c C-t= (or =S-<right>= / =S-<left>=). The keyword you cycle to is matched back to a team state by the same slug rule, so cycling =TODO= → =IN-PROGRESS= moves the ticket to the "In Progress" state. A keyword no team state slugifies to can't be resolved, so that save is reported skipped.
+- Cycle the keyword with =C-c C-t= (or =S-<right>= / =S-<left>=). The keyword you cycle to is matched back to a team state by the same slug rule, so cycling =TODO= → =IN-PROGRESS= moves the issue to the "In Progress" state. A keyword no team state slugifies to can't be resolved, so that save is reported skipped.
- =pearl-edit-state= completes over every state on the team and writes the keyword for you. Use it to reach a state whose keyword you don't remember, or one the visible keyword set doesn't cover.
** The Org File
diff --git a/pearl.el b/pearl.el
index f08f636..8f7fa3c 100644
--- a/pearl.el
+++ b/pearl.el
@@ -1157,7 +1157,7 @@ CALLBACK is called with the created issue data."
(pearl--graphql-request-async query variables success-fn error-fn)))
-(defun pearl-create-issue (title description team-id)
+(defun pearl--create-issue (title description team-id)
"Create a new issue (synchronous wrapper for backward compatibility).
TITLE is the issue title.
DESCRIPTION is the issue description.
@@ -2577,7 +2577,7 @@ create callback fires with another buffer current (the compose path)."
(message "Added comment to %s" issue-id)))))
;;;###autoload
-(defun pearl-add-comment (&optional body)
+(defun pearl-create-comment (&optional body)
"Add a comment to the Linear issue at point.
Interactively, opens an Org compose buffer (C-c C-c sends, C-c C-k cancels) and
converts the composed Org to Markdown before sending -- room to write a real
@@ -2946,7 +2946,7 @@ yields the hardcoded default. Pure function, no side effects."
;; Affordance preamble (org comments -- not rendered content).
(insert "#\n")
(insert "# Body = the issue description; edit it, then M-x pearl-save-issue to push.\n")
- (insert "# Comments subtree = the thread; add with M-x pearl-add-comment.\n")
+ (insert "# Comments subtree = the thread; add with M-x pearl-create-comment.\n")
(insert "# State/assignee/labels: M-x pearl-edit-state / -assignee / -labels; priority: the org cookie (C-c ,). All push at save.\n")
(insert "# Refresh with M-x pearl-refresh-current-view (whole file) or -current-issue (one).\n")
(insert "\n")
@@ -3552,7 +3552,7 @@ the deferred smerge path."
"Save the description of the issue subtree at MARKER, calling CALLBACK.
A push advances both `LINEAR-DESC-SHA256' (the markdown baseline used by the
remote conflict gate) and `LINEAR-DESC-ORG-SHA256' (the rendered-Org baseline
-used by the local dirty scan), so the next scan sees the ticket as clean."
+used by the local dirty scan), so the next scan sees the issue as clean."
(org-with-point-at marker
(let* ((issue-id (org-entry-get nil "LINEAR-ID"))
(identifier (org-entry-get nil "LINEAR-IDENTIFIER"))
@@ -4147,7 +4147,7 @@ yours\". Returns a plist (:issues :titles :descriptions :priorities :states
VIEWER-ID gates comment ownership; VIEWER-FAILED marks a failed viewer lookup so
the prompt counts comments as viewer-unavailable rather than read-only. BUFFER
is surfaced if anything pushed. Declining the prompt mutates nothing. The
-queue continues past a per-field conflict, so one conflicted ticket does not
+queue continues past a per-field conflict, so one conflicted issue does not
stop the rest."
(if (not (y-or-n-p (pearl--save-all-prompt
(pearl--save-all-counts scan viewer-id viewer-failed))))
@@ -4195,7 +4195,7 @@ reported. A clean file does nothing."
"Non-nil if the issue subtree at point has any unpushed local edit.
Broader than `pearl--subtree-dirty-p', which inspects only the description:
this also covers a changed title, state, priority, assignee, labels, or comment.
-A merge refresh uses it so it keeps a ticket you have touched in any field, not
+A merge refresh uses it so it keeps an issue you have touched in any field, not
just ones whose description changed."
(pearl--issue-has-dirty-fields-p (pearl--issue-dirty-fields)))
@@ -4477,7 +4477,7 @@ resolves to a single label."
(when (and selected-label-name (not (string-empty-p selected-label-name)))
(cdr (assoc selected-label-name issue-types)))))
-(defun pearl-new-issue ()
+(defun pearl-create-issue ()
"Create a new Linear issue with additional attributes."
(interactive)
;; Select team first (needed for states, members, etc.)
@@ -4846,9 +4846,9 @@ body stay."
;; delete); fetch / view / setup are the operational groups with no keymap
;; equivalent. The org-sync commands (enable / disable / push-file) are
;; deliberately not surfaced -- they are plumbing (see the todo).
- ["Ticket"
+ ["Issue"
["Save"
- ("e" "save ticket" pearl-save-issue)
+ ("e" "save issue" pearl-save-issue)
("E" "save all" pearl-save-all)]
["Edit"
("D" "edit description" pearl-edit-description)
@@ -4856,11 +4856,11 @@ body stay."
("s" "edit state" pearl-edit-state)
("a" "edit assignee" pearl-edit-assignee)
("L" "edit labels" pearl-edit-labels)]
- ["New"
- ("n" "new ticket" pearl-new-issue)
- ("c" "new comment" pearl-add-comment)]
+ ["Create"
+ ("n" "create issue" pearl-create-issue)
+ ("c" "create comment" pearl-create-comment)]
["Delete"
- ("k" "delete ticket" pearl-delete-current-issue)
+ ("k" "delete issue" pearl-delete-current-issue)
("K" "delete comment" pearl-delete-current-comment)]]
["Workspace"
["Fetch"
@@ -4873,7 +4873,7 @@ body stay."
("g" "refresh view" pearl-refresh-current-view)
("r" "refresh issue" pearl-refresh-current-issue)
("b" "open view in Linear" pearl-open-current-view-in-linear)
- ("o" "open ticket in browser" pearl-open-current-issue)]
+ ("o" "open issue in browser" pearl-open-current-issue)]
["Setup"
("T" "test connection" pearl-test-connection)
("C" "check setup" pearl-check-setup)
@@ -4922,27 +4922,32 @@ body stay."
map)
"Pearl edit commands; a sub-keymap of `pearl-prefix-map'.")
-(defvar pearl-new-map
+(defvar pearl-create-map
(let ((map (make-sparse-keymap)))
- (define-key map "t" (cons "new ticket" #'pearl-new-issue))
- (define-key map "c" (cons "new comment" #'pearl-add-comment))
+ (define-key map "t" (cons "create issue" #'pearl-create-issue))
+ (define-key map "c" (cons "create comment" #'pearl-create-comment))
map)
- "Pearl new-object commands; a sub-keymap of `pearl-prefix-map'.")
+ "Pearl create commands; a sub-keymap of `pearl-prefix-map'.")
(defvar pearl-delete-map
(let ((map (make-sparse-keymap)))
- (define-key map "t" (cons "delete ticket" #'pearl-delete-current-issue))
+ (define-key map "t" (cons "delete issue" #'pearl-delete-current-issue))
(define-key map "c" (cons "delete comment" #'pearl-delete-current-comment))
map)
"Pearl delete commands; a sub-keymap of `pearl-prefix-map'.")
-(defvar pearl-url-map
+(defvar pearl-open-map
(let ((map (make-sparse-keymap)))
- (define-key map "o" (cons "open issue in browser" #'pearl-open-current-issue))
- (define-key map "c" (cons "copy issue URL" #'pearl-copy-issue-url))
+ (define-key map "i" (cons "open issue in browser" #'pearl-open-current-issue))
(define-key map "v" (cons "open view in Linear" #'pearl-open-current-view-in-linear))
map)
- "Pearl URL commands; a sub-keymap of `pearl-prefix-map'.")
+ "Pearl open commands; a sub-keymap of `pearl-prefix-map'.")
+
+(defvar pearl-copy-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "u" (cons "copy issue URL" #'pearl-copy-issue-url))
+ map)
+ "Pearl copy commands; a sub-keymap of `pearl-prefix-map'.")
(defvar pearl-prefix-map
(let ((map (make-sparse-keymap)))
@@ -4950,21 +4955,21 @@ body stay."
(define-key map "l" (cons "list my open issues" #'pearl-list-issues))
(define-key map "g" (cons "refresh view" #'pearl-refresh-current-view))
(define-key map "r" (cons "refresh issue" #'pearl-refresh-current-issue))
- (define-key map "s" (cons "save ticket" #'pearl-save-issue))
+ (define-key map "s" (cons "save issue" #'pearl-save-issue))
(define-key map "S" (cons "save all" #'pearl-save-all))
(define-key map "d" (cons "edit description" #'pearl-edit-description))
- (define-key map "c" (cons "new comment" #'pearl-add-comment))
(define-key map "m" (cons "menu" #'pearl-menu))
- ;; Category sub-maps -- two keys.
+ ;; Category sub-maps -- two keys; each prefix names an action verb.
(define-key map "f" (cons "+fetch" pearl-fetch-map))
(define-key map "e" (cons "+edit" pearl-edit-map))
- (define-key map "n" (cons "+new" pearl-new-map))
+ (define-key map "c" (cons "+create" pearl-create-map))
(define-key map "k" (cons "+delete" pearl-delete-map))
- (define-key map "u" (cons "+url" pearl-url-map))
+ (define-key map "o" (cons "+open" pearl-open-map))
+ (define-key map "y" (cons "+copy" pearl-copy-map))
map)
"Pearl command prefix keymap.
-Hot-path commands are bound directly; the rest live under the category
-sub-maps (fetch / edit / new / delete / url). `pearl-mode' binds this under
+Hot-path commands are bound directly; the rest live under verb sub-maps
+\(fetch / edit / create / delete / open / copy). `pearl-mode' binds this under
`pearl-keymap-prefix' inside Pearl buffers; you can also bind it globally
\(see the Commentary above).")
diff --git a/tests/test-integration-acceptance.el b/tests/test-integration-acceptance.el
index 48dd70a..37dce34 100644
--- a/tests/test-integration-acceptance.el
+++ b/tests/test-integration-acceptance.el
@@ -36,7 +36,7 @@
;; - --read-active-source / --build-org-content header round-trip
;; - pearl-refresh-current-view -> --merge-query-result (in-place merge)
;; - pearl-sync-current-issue -> --sync-decision -> --update-issue-description-async
-;; - pearl-add-comment -> --create-comment-async -> --append-comment-to-issue
+;; - pearl-create-comment -> --create-comment-async -> --append-comment-to-issue
;; - edit the priority cookie + pearl-save-issue -> --save-priority-field -> --update-issue-async
;;; Code:
@@ -189,7 +189,7 @@ because the description-update mutation contains both `IssueDescription' and
(goto-char (point-min))
(re-search-forward "First issue")
(setq test-integration--ops nil)
- (pearl-add-comment "looks good")
+ (pearl-create-comment "looks good")
(should (memq 'comment-create test-integration--ops))
(goto-char (point-min))
(should (re-search-forward "^\\*\\*\\* 💬 Comments 1/1$" nil t))
diff --git a/tests/test-pearl-commands.el b/tests/test-pearl-commands.el
index 77f2d46..ccd39c8 100644
--- a/tests/test-pearl-commands.el
+++ b/tests/test-pearl-commands.el
@@ -98,9 +98,9 @@
(pearl-list-issues-by-project)
(should-not called))))
-;;; pearl-new-issue
+;;; pearl-create-issue
-(ert-deftest test-pearl-new-issue-success-returns-issue ()
+(ert-deftest test-pearl-create-issue-success-returns-issue ()
"new-issue builds the input, sends it, and returns the created issue."
(let ((pearl-default-team-id "team-1"))
(cl-letf (((symbol-function 'read-string)
@@ -120,13 +120,13 @@
(lambda (_q &optional _v)
'((data (issueCreate (success . t)
(issue (id . "i1") (identifier . "ENG-1") (title . "My Issue"))))))))
- (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-new-issue))))))))
+ (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-create-issue))))))))
-(ert-deftest test-pearl-new-issue-no-team-stops ()
+(ert-deftest test-pearl-create-issue-no-team-stops ()
"new-issue stops cleanly when no team is selected."
(let ((pearl-default-team-id nil))
(cl-letf (((symbol-function 'pearl-select-team) (lambda () nil)))
- (should (progn (pearl-new-issue) t)))))
+ (should (progn (pearl-create-issue) t)))))
;;; pearl-test-connection / check-setup
diff --git a/tests/test-pearl-comment-editing.el b/tests/test-pearl-comment-editing.el
index 4853548..5d5204a 100644
--- a/tests/test-pearl-comment-editing.el
+++ b/tests/test-pearl-comment-editing.el
@@ -328,7 +328,7 @@ the refresh / add-comment / view-merge paths, never on the initial render."
(when (find-buffer-visiting tmp) (kill-buffer (find-buffer-visiting tmp)))
(ignore-errors (delete-file tmp)))))
-(ert-deftest test-pearl-add-comment-from-inside-comment-walks-up ()
+(ert-deftest test-pearl-create-comment-from-inside-comment-walks-up ()
"From inside a comment subtree, add-comment walks up to the enclosing issue and
adds the comment there rather than refusing."
(test-pearl--in-org (test-pearl--comment-doc "u-me" "stored" "uniquecommentline")
@@ -340,7 +340,7 @@ adds the comment there rather than refusing."
:created-at "2026-05-26T00:00:00.000Z" :body "hi")))))
(goto-char (point-min))
(re-search-forward "uniquecommentline") ; point inside the comment subtree
- (pearl-add-comment "hi"))
+ (pearl-create-comment "hi"))
;; resolved the enclosing issue's LINEAR-ID ("a"), not the comment's
(should (equal "a" created-on)))))
diff --git a/tests/test-pearl-comments.el b/tests/test-pearl-comments.el
index 99b705a..323d7b7 100644
--- a/tests/test-pearl-comments.el
+++ b/tests/test-pearl-comments.el
@@ -23,7 +23,7 @@
;; Comments subtree (ordered per `pearl-comment-sort-order'), including comments
;; in the issue render, the commentCreate helper (stubbed at the HTTP boundary),
;; the in-place append under the Comments subtree (creating it when absent), and the
-;; `pearl-add-comment' command.
+;; `pearl-create-comment' command.
;;; Code:
@@ -165,9 +165,9 @@
(should (< (progn (re-search-forward "first") (point))
(progn (re-search-forward "second") (point)))))))
-;;; pearl-add-comment
+;;; pearl-create-comment
-(ert-deftest test-pearl-add-comment-appends-returned-comment ()
+(ert-deftest test-pearl-create-comment-appends-returned-comment ()
"The command creates a comment and inserts the returned one in the buffer."
(test-pearl--in-org
"*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n"
@@ -176,25 +176,25 @@
(funcall cb (list :id "c1" :author "Craig"
:created-at "2026-05-23T14:00:00.000Z" :body body)))))
(re-search-forward "Body.")
- (pearl-add-comment "my new comment")
+ (pearl-create-comment "my new comment")
(goto-char (point-min))
(should (re-search-forward "^\\*\\*\\* 💬 Comments 1/1$" nil t))
(should (re-search-forward "my new comment" nil t)))))
-(ert-deftest test-pearl-add-comment-reports-failure ()
+(ert-deftest test-pearl-create-comment-reports-failure ()
"A failed create does not insert a Comments subtree."
(test-pearl--in-org
"*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n"
(cl-letf (((symbol-function 'pearl--create-comment-async)
(lambda (_id _body cb) (funcall cb nil))))
- (pearl-add-comment "x")
+ (pearl-create-comment "x")
(goto-char (point-min))
(should-not (re-search-forward "^\\*\\*\\* Comments$" nil t)))))
-(ert-deftest test-pearl-add-comment-not-on-issue-errors ()
+(ert-deftest test-pearl-create-comment-not-on-issue-errors ()
"Adding a comment outside a Linear issue heading signals a user error."
(test-pearl--in-org "* Plain heading\nno id\n"
- (should-error (pearl-add-comment "x") :type 'user-error)))
+ (should-error (pearl-create-comment "x") :type 'user-error)))
(ert-deftest test-pearl-append-comment-targets-current-issue-not-sibling ()
"Appending adds under the current issue's Comments, not a later issue's subtree."
diff --git a/tests/test-pearl-compose.el b/tests/test-pearl-compose.el
index 3e62c09..4f4d611 100644
--- a/tests/test-pearl-compose.el
+++ b/tests/test-pearl-compose.el
@@ -21,7 +21,7 @@
;; Tests for the shared compose buffer (`pearl--compose-in-buffer' and its
;; submit/abort/body helpers) and the two commands wired onto it:
-;; `pearl-add-comment' (interactive composer path) and
+;; `pearl-create-comment' (interactive composer path) and
;; `pearl-edit-description'. The body is extracted below a read-only
;; header and converted Org->Markdown by the callers.
@@ -102,7 +102,7 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs
(re-search-forward "Body\\.")
,@body)))
-(ert-deftest test-pearl-add-comment-interactive-composes-and-converts ()
+(ert-deftest test-pearl-create-comment-interactive-composes-and-converts ()
"An interactive add-comment composes Org and sends the Markdown conversion."
(test-pearl--in-issue
(let (sent)
@@ -113,11 +113,11 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs
(funcall on-finish "see *bold* and `code`")))
((symbol-function 'pearl--create-comment-async)
(lambda (_id body _cb) (setq sent body))))
- (pearl-add-comment) ; interactive form: body nil
+ (pearl-create-comment) ; interactive form: body nil
;; Org markup converted to Markdown before sending
(should (string= "see **bold** and `code`" sent))))))
-(ert-deftest test-pearl-add-comment-with-body-skips-composer ()
+(ert-deftest test-pearl-create-comment-with-body-skips-composer ()
"A non-interactive add-comment with BODY sends it directly, no composer."
(test-pearl--in-issue
(let ((composed nil) sent)
@@ -125,7 +125,7 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs
(lambda (&rest _) (setq composed t)))
((symbol-function 'pearl--create-comment-async)
(lambda (_id body _cb) (setq sent body))))
- (pearl-add-comment "literal body")
+ (pearl-create-comment "literal body")
(should-not composed)
(should (string= "literal body" sent))))))
diff --git a/tests/test-pearl-keymap.el b/tests/test-pearl-keymap.el
index 25b63d9..b465395 100644
--- a/tests/test-pearl-keymap.el
+++ b/tests/test-pearl-keymap.el
@@ -46,23 +46,24 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)."
(should (keymapp pearl-prefix-map)))
(ert-deftest test-pearl-prefix-map-direct-hot-keys ()
- "The hot-path commands resolve in one key under the prefix."
+ "The hot-path commands resolve in one key under the prefix.
+The lowercase `c' is no longer a direct command -- it is the create prefix."
(should (eq 'pearl-list-issues (lookup-key pearl-prefix-map (kbd "l"))))
(should (eq 'pearl-refresh-current-view (lookup-key pearl-prefix-map (kbd "g"))))
(should (eq 'pearl-refresh-current-issue (lookup-key pearl-prefix-map (kbd "r"))))
(should (eq 'pearl-save-issue (lookup-key pearl-prefix-map (kbd "s"))))
(should (eq 'pearl-save-all (lookup-key pearl-prefix-map (kbd "S"))))
(should (eq 'pearl-edit-description (lookup-key pearl-prefix-map (kbd "d"))))
- (should (eq 'pearl-add-comment (lookup-key pearl-prefix-map (kbd "c"))))
(should (eq 'pearl-menu (lookup-key pearl-prefix-map (kbd "m")))))
-(ert-deftest test-pearl-prefix-map-sub-prefixes-are-keymaps ()
- "The category keys are sub-prefixes, not commands."
- (should (keymapp (lookup-key pearl-prefix-map (kbd "f"))))
- (should (keymapp (lookup-key pearl-prefix-map (kbd "e"))))
- (should (keymapp (lookup-key pearl-prefix-map (kbd "n"))))
- (should (keymapp (lookup-key pearl-prefix-map (kbd "k"))))
- (should (keymapp (lookup-key pearl-prefix-map (kbd "u")))))
+(ert-deftest test-pearl-prefix-map-sub-prefixes-are-verbs ()
+ "Each category key is a sub-prefix keymap named for an action verb."
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "f")))) ; fetch
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "e")))) ; edit
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "c")))) ; create
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "k")))) ; delete
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "o")))) ; open
+ (should (keymapp (lookup-key pearl-prefix-map (kbd "y"))))) ; copy
(ert-deftest test-pearl-prefix-map-fetch-group ()
"The fetch group resolves each source command."
@@ -82,27 +83,24 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)."
;; priority has no edit command (org-native cookie); e p is unbound
(should-not (lookup-key pearl-prefix-map (kbd "e p"))))
-(ert-deftest test-pearl-prefix-map-new-and-delete-groups ()
- "New and delete groups resolve ticket/comment, with delete under k."
- (should (eq 'pearl-new-issue (lookup-key pearl-prefix-map (kbd "n t"))))
- (should (eq 'pearl-add-comment (lookup-key pearl-prefix-map (kbd "n c"))))
+(ert-deftest test-pearl-prefix-map-create-and-delete-groups ()
+ "Create and delete groups resolve issue/comment under c and k."
+ (should (eq 'pearl-create-issue (lookup-key pearl-prefix-map (kbd "c t"))))
+ (should (eq 'pearl-create-comment (lookup-key pearl-prefix-map (kbd "c c"))))
(should (eq 'pearl-delete-current-issue (lookup-key pearl-prefix-map (kbd "k t"))))
(should (eq 'pearl-delete-current-comment (lookup-key pearl-prefix-map (kbd "k c")))))
-(ert-deftest test-pearl-prefix-map-url-group ()
- "The url group resolves open-in-browser, copy-url, and open-view."
- (should (eq 'pearl-open-current-issue (lookup-key pearl-prefix-map (kbd "u o"))))
- (should (eq 'pearl-copy-issue-url (lookup-key pearl-prefix-map (kbd "u c"))))
- (should (eq 'pearl-open-current-view-in-linear (lookup-key pearl-prefix-map (kbd "u v")))))
+(ert-deftest test-pearl-prefix-map-open-and-copy-groups ()
+ "The open group resolves the two open actions; copy resolves copy-url."
+ (should (eq 'pearl-open-current-issue (lookup-key pearl-prefix-map (kbd "o i"))))
+ (should (eq 'pearl-open-current-view-in-linear (lookup-key pearl-prefix-map (kbd "o v"))))
+ (should (eq 'pearl-copy-issue-url (lookup-key pearl-prefix-map (kbd "y u")))))
(ert-deftest test-pearl-prefix-map-common-commands-bound-twice ()
"The most common commands resolve both directly and inside their group."
;; edit-description: direct d and e d
(should (eq (lookup-key pearl-prefix-map (kbd "d"))
(lookup-key pearl-prefix-map (kbd "e d"))))
- ;; new comment: direct c and n c
- (should (eq (lookup-key pearl-prefix-map (kbd "c"))
- (lookup-key pearl-prefix-map (kbd "n c"))))
;; list issues: direct l and f o
(should (eq (lookup-key pearl-prefix-map (kbd "l"))
(lookup-key pearl-prefix-map (kbd "f o")))))
@@ -111,12 +109,14 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)."
"Leaf bindings carry verb labels; sub-prefixes carry +labels."
(should (string= "edit state"
(test-pearl-keymap--label-of pearl-edit-map 'pearl-edit-state)))
- (should (string= "new ticket"
- (test-pearl-keymap--label-of pearl-new-map 'pearl-new-issue)))
+ (should (string= "create issue"
+ (test-pearl-keymap--label-of pearl-create-map 'pearl-create-issue)))
(should (string= "open issue in browser"
- (test-pearl-keymap--label-of pearl-url-map 'pearl-open-current-issue)))
+ (test-pearl-keymap--label-of pearl-open-map 'pearl-open-current-issue)))
+ (should (string= "copy issue URL"
+ (test-pearl-keymap--label-of pearl-copy-map 'pearl-copy-issue-url)))
(should (string= "+edit" (test-pearl-keymap--label-of pearl-prefix-map pearl-edit-map)))
- (should (string= "+url" (test-pearl-keymap--label-of pearl-prefix-map pearl-url-map))))
+ (should (string= "+open" (test-pearl-keymap--label-of pearl-prefix-map pearl-open-map))))
(ert-deftest test-pearl-prefix-map-not-bound-at-load ()
"Loading pearl binds nothing globally; the keys are live only via pearl-mode."
diff --git a/tests/test-pearl-menu.el b/tests/test-pearl-menu.el
index 5964d31..63b925c 100644
--- a/tests/test-pearl-menu.el
+++ b/tests/test-pearl-menu.el
@@ -65,8 +65,8 @@ menu entry that still points at it fails here."
pearl-save-issue
pearl-save-all
pearl-edit-state
- pearl-add-comment
- pearl-new-issue
+ pearl-create-comment
+ pearl-create-issue
pearl-delete-current-issue))
(should (memq expected cmds)))))
diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el
index 2f7b312..47b48c2 100644
--- a/tests/test-pearl-save.el
+++ b/tests/test-pearl-save.el
@@ -600,7 +600,7 @@ with no push."
(ert-deftest test-pearl-save-description-advances-both-hashes ()
"A description push advances both the markdown and the Org provenance hashes,
-so the next local dirty scan (which uses the Org hash) sees the ticket as clean."
+so the next local dirty scan (which uses the Org hash) sees the issue as clean."
(test-pearl-save--in-rendered (test-pearl-save--issue)
(re-search-forward "Original description\\.")
(end-of-line)
diff --git a/tests/test-pearl-smoke.el b/tests/test-pearl-smoke.el
index 53c4d06..760da2a 100644
--- a/tests/test-pearl-smoke.el
+++ b/tests/test-pearl-smoke.el
@@ -31,7 +31,7 @@
"The package source loads and defines its public commands."
(should (featurep 'pearl))
(should (fboundp 'pearl-list-issues))
- (should (fboundp 'pearl-new-issue)))
+ (should (fboundp 'pearl-create-issue)))
(provide 'test-pearl-smoke)
;;; test-pearl-smoke.el ends here
diff --git a/tests/test-pearl-sync-wrappers.el b/tests/test-pearl-sync-wrappers.el
index a5c9c70..cb4ac96 100644
--- a/tests/test-pearl-sync-wrappers.el
+++ b/tests/test-pearl-sync-wrappers.el
@@ -43,11 +43,11 @@
'((data (team (states (nodes . (((id . "s1") (name . "Todo")))))) ))
(should (= 1 (length (pearl-get-states "team-1"))))))
-(ert-deftest test-pearl-create-issue-sync-returns-issue ()
+(ert-deftest test-pearl--create-issue-sync-returns-issue ()
"The sync create wrapper returns the created issue node."
(testutil-linear-with-response
'((data (issueCreate (success . t) (issue (id . "i1") (identifier . "ENG-1") (title . "T")))))
- (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-create-issue "T" "" "team")))))))
+ (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl--create-issue "T" "" "team")))))))
(ert-deftest test-pearl-sync-wrapper-times-out-without-callback ()
"A sync wrapper returns nil instead of hanging when no callback fires."