From 0dccaaee955c99d2affcf33c145feba2ef1fdcda Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 2 Jun 2026 11:56:55 -0500 Subject: feat(keys): dedicated views group, de-overloaded fetch, account binding The C-; L keymap and the transient menu had drifted into two different letter schemes for the same commands, the fetch sub-map carried both issue-fetching and view-movement, and pearl-switch-account was bound nowhere. Give views their own group under v: l/L run local/Linear, c/e/k create/edit/delete, u/U publish local/current, d save a Linear view locally. Fetch (f) is now issue sources only. A new workspace group (w) binds switch-account plus the setup commands, which previously lived only in the transient. v D is reserved, unbound, for the not-yet-built set-default-view. The transient already had an issue-only fetch group and a dedicated views group, so its changes are additive: switch-account and copy-issue-url were both missing from the menu and are added, and save/save-all/edit-description take s/S/d to match the keymap's hot keys. Matching those three cascades a few view and edit letters because the transient is a flat keyspace where every command needs a unique key. Suite 789 to 792, compile and lint clean. --- README.org | 30 +++++++------ pearl.el | 109 ++++++++++++++++++++++++++++++--------------- tests/test-pearl-keymap.el | 82 +++++++++++++++++++++++++--------- tests/test-pearl-menu.el | 6 ++- 4 files changed, 154 insertions(+), 73 deletions(-) diff --git a/README.org b/README.org index 531421b..fc53d9e 100644 --- a/README.org +++ b/README.org @@ -159,7 +159,7 @@ 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 sub-maps, one per action verb. 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 or noun. Fetch is issue sources only; views, account switching, and setup each have their own group. The common ones appear in both places, so =d= and =e d= both edit the description. | Key | Command | |-----------+----------------------------------------------------------------------| @@ -169,12 +169,14 @@ The hot-path commands sit one key under the prefix; the rest are grouped into su | =s= / =S= | save the issue at point / save every issue in the file | | =d= | edit the description | | =m= | open the full transient menu | -| =f= ... | fetch: =s= pick source, =o= open issues, =p= by project, =f= filter, =l= run local view, =v= run Linear view, =u= publish, =d= save Linear view locally, =P= publish current | -| =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment, =v= local view | -| =c= ... | create: =t= issue, =c= comment, =v= local view | -| =k= ... | delete: =t= issue, =c= comment, =v= local view | +| =f= ... | fetch (issue sources): =s= pick source, =o= open issues, =p= by project, =f= filter | +| =v= ... | views: =l= run local, =L= run Linear, =c= create, =e= edit, =k= delete, =u= publish, =U= publish current, =d= save Linear view locally, =D= set default (reserved) | +| =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment | +| =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 | +| =w= ... | workspace: =a= switch account, =t= test connection, =c= check setup, =!= toggle debug, =x= clear cache | To reach the map outside a Pearl buffer, bind it globally as well: @@ -206,14 +208,14 @@ If the favorites fetch fails (network/auth/transport), the picker still offers a *** Publishing a local view as a Linear view -A local view is great for your own quick filters, but the team can't see it and the Linear web UI can't run it. =pearl-publish-local-view= (=C-; L f u=, or =u= in the transient's Views group) promotes a local view to a Linear Custom View so it's visible alongside your sidebar favorites and the rest of the team's views. This is the *copy up* direction of the lifecycle. +A local view is great for your own quick filters, but the team can't see it and the Linear web UI can't run it. =pearl-publish-local-view= (=C-; L v u=, or =u= in the transient's Views group) promotes a local view to a Linear Custom View so it's visible alongside your sidebar favorites and the rest of the team's views. This is the *copy up* direction of the lifecycle. Two commands publish: | Command | Binding | What it does | |------------------------------------+--------------------------------------+--------------------------------------------------------------------| -| =pearl-publish-local-view= | =C-; L f u=, =u= in the transient Views group | Pick a local view by name and publish (or update) it on Linear | -| =pearl-publish-current-view= | =C-; L f P=, =U= in the transient Views group | Read the buffer's =#+LINEAR-SOURCE=; if it names a local view, publish that one | +| =pearl-publish-local-view= | =C-; L v u=, =u= in the transient Views group | Pick a local view by name and publish (or update) it on Linear | +| =pearl-publish-current-view= | =C-; L v U=, =U= in the transient Views group | Read the buffer's =#+LINEAR-SOURCE=; if it names a local view, publish that one | The first time you publish a local view, Pearl asks where the view should live with one enriched prompt that spells out the full end-state per option: @@ -234,11 +236,11 @@ After a successful publish, the local-view entry gains four metadata keys (=:lin Pearl's =:sort= / =:order= on a local view don't publish up in v1 -- Linear's =CustomView= API has no sort input, so a published view renders in whatever order Linear's defaults give it. If sort order is load-bearing for the view, set it in the Linear web UI after the first publish. -=pearl-delete-local-view= (=C-; L k v=) on a published entry asks a second question after the local-delete confirmation: also delete the linked Linear view? Yes calls =customViewDelete=, which Linear handles as a soft delete (recoverable from the workspace trash). No unlinks the entry locally and leaves the Linear view in place. If the Linear delete fails (permissions, network), Pearl prompts to drop the local entry anyway -- accepting orphans the Linear view, and the message names the view id explicitly so you can clean it up by hand. Under multiple accounts, publish and delete refuse a local view tagged to a non-active account before touching Linear. +=pearl-delete-local-view= (=C-; L v k=) on a published entry asks a second question after the local-delete confirmation: also delete the linked Linear view? Yes calls =customViewDelete=, which Linear handles as a soft delete (recoverable from the workspace trash). No unlinks the entry locally and leaves the Linear view in place. If the Linear delete fails (permissions, network), Pearl prompts to drop the local entry anyway -- accepting orphans the Linear view, and the message names the view id explicitly so you can clean it up by hand. Under multiple accounts, publish and delete refuse a local view tagged to a non-active account before touching Linear. *** Saving a Linear view locally -The other direction -- *copy down* -- forks a Linear view into a new, editable local view. =pearl-save-linear-view-locally= (=C-; L f d=, or =d= in the transient's Views group) lists your favorited Linear views, fetches the chosen view's filter, and reverse-compiles it into a Pearl authoring filter you can then edit and rename. The new local view is independent: it carries no tracking link, so editing or renaming it never touches the Linear view (publish it back later to push your changes up as a new Linear view). +The other direction -- *copy down* -- forks a Linear view into a new, editable local view. =pearl-save-linear-view-locally= (=C-; L v d=, or =D= in the transient's Views group) lists your favorited Linear views, fetches the chosen view's filter, and reverse-compiles it into a Pearl authoring filter you can then edit and rename. The new local view is independent: it carries no tracking link, so editing or renaming it never touches the Linear view (publish it back later to push your changes up as a new Linear view). Linear stores a view's filter as an =and=/=or= tree that's richer than Pearl's AND-only local model. Copy-down works for any view inside that model -- a team / state / label / assignee filter, the common case. When a view uses something Pearl can't represent (OR logic, a label parent, a due-date filter, or a multi-value filter on a singular dimension like project or assignee), copy-down *refuses and names the construct* rather than silently saving a filter that matches a different set of issues; run such a view directly with =pearl-run-linear-view= instead. To copy down *all* Linear views (not just favorited ones) is a later addition; v1 lists favorites. @@ -246,9 +248,9 @@ Linear stores a view's filter as an =and=/=or= tree that's richer than Pearl's A | Command | Binding | What it does | |------------------------------+----------------+------------------------------------------------------| -| =pearl-create-local-view= | =C-; L c v= | Build a filter interactively and save it as a local view | -| =pearl-edit-local-view= | =C-; L e v= | Edit a local view's filter, name, sort, and order, preserving its metadata | -| =pearl-save-linear-view-locally= | =C-; L f d= | Copy a favorited Linear view down into a new editable local view | +| =pearl-create-local-view= | =C-; L v c= | Build a filter interactively and save it as a local view | +| =pearl-edit-local-view= | =C-; L v e= | Edit a local view's filter, name, sort, and order, preserving its metadata | +| =pearl-save-linear-view-locally= | =C-; L v d= | Copy a favorited Linear view down into a new editable local view | =pearl-list-issues-filtered= also offers to save its ad-hoc filter as a local view at the end. Create, edit, and copy-down all prompt Replace / Rename / Cancel on a name collision, and under multiple accounts stamp the active account on a new view and refuse a cross-account edit. @@ -419,7 +421,7 @@ For per-URL routing (e.g. "Linear goes to the work-account browser, everything e :CUSTOM_ID: multiple-accounts :END: -If you work more than one Linear workspace — say a work account and a personal one — set =pearl-accounts= and switch between them with =M-x pearl-switch-account=. Each account names where its key is found, which Org file holds its issues, and (optionally) a default team: +If you work more than one Linear workspace — say a work account and a personal one — set =pearl-accounts= and switch between them with =pearl-switch-account= (=C-; L w a=, or =M-x pearl-switch-account=). Each account names where its key is found, which Org file holds its issues, and (optionally) a default team: #+begin_src elisp (setq pearl-accounts diff --git a/pearl.el b/pearl.el index 6076c81..799a195 100644 --- a/pearl.el +++ b/pearl.el @@ -6584,20 +6584,25 @@ body stay." ;;;###autoload (autoload 'pearl-menu "pearl" nil t) (transient-define-prefix pearl-menu () "Dispatch menu for pearl commands." - ;; Grouped to mirror the `pearl-prefix-map' categories (save / edit / new / - ;; 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). + ;; Grouped to mirror the `pearl-prefix-map' categories: Fetch is issue + ;; sources only, Views is its own group, Workspace holds account switching + ;; plus setup. The hot keys `s' / `S' / `d' (save / save-all / + ;; edit-description) match the keymap's direct keys. The transient is a + ;; flat keyspace -- every command needs a unique key on the screen -- so the + ;; remaining letters are read off the menu rather than matching the keymap's + ;; two-key chains. Set-default-view has no command yet, so it gets no slot + ;; here (the keymap reserves `v D' for it). The org-sync commands (enable / + ;; disable / push-file) are deliberately not surfaced -- they are plumbing. ["Issue" ["Save" - ("e" "save issue" pearl-save-issue) - ("E" "save all" pearl-save-all)] + ("s" "save issue" pearl-save-issue) + ("S" "save all" pearl-save-all)] ["Edit" - ("D" "edit description" pearl-edit-description) - ("M" "edit comment" pearl-edit-current-comment) - ("s" "edit state" pearl-edit-state) + ("d" "edit description" pearl-edit-description) + ("t" "edit state" pearl-edit-state) ("a" "edit assignee" pearl-edit-assignee) - ("L" "edit labels" pearl-edit-labels)] + ("L" "edit labels" pearl-edit-labels) + ("M" "edit comment" pearl-edit-current-comment)] ["Create" ("n" "create issue" pearl-create-issue) ("c" "create comment" pearl-create-comment)] @@ -6612,19 +6617,21 @@ body stay." ("f" "build a filter" pearl-list-issues-filtered)] ["Views" ("V" "create local view" pearl-create-local-view) - ("y" "edit local view" pearl-edit-local-view) + ("e" "edit local view" pearl-edit-local-view) ("Q" "run local view" pearl-run-local-view) ("v" "run Linear view" pearl-run-linear-view) ("u" "publish local view" pearl-publish-local-view) - ("d" "save Linear view locally" pearl-save-linear-view-locally) + ("D" "save Linear view locally" pearl-save-linear-view-locally) ("U" "publish current view" pearl-publish-current-view) ("X" "delete local view" pearl-delete-local-view)] ["Buffer" ("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 issue in browser" pearl-open-current-issue)] - ["Setup" + ("o" "open issue in browser" pearl-open-current-issue) + ("y" "copy issue URL" pearl-copy-issue-url)] + ["Workspace / setup" + ("A" "switch account" pearl-switch-account) ("T" "test connection" pearl-test-connection) ("C" "check setup" pearl-check-setup) ("!" "toggle debug" pearl-toggle-debug) @@ -6634,9 +6641,10 @@ body stay." ;; ;; A prefix keymap that makes the whole command surface reachable from the ;; keyboard. The hot-path commands sit directly under the prefix (one key); -;; the rest are grouped into category sub-maps (two keys). The most common -;; commands appear in both places -- e.g. edit-description is the direct `d' -;; and also `e d' under the edit group. +;; the rest are grouped into category sub-maps (two keys): fetch (issue +;; sources), views, edit, create, delete, open, copy, and workspace (account +;; + setup). The most common commands appear in both places -- e.g. +;; edit-description is the direct `d' and also `e d' under the edit group. ;; ;; Each binding is a (LABEL . COMMAND) menu item, so `which-key' shows the ;; verb-matched label without pearl depending on which-key. Lookups still @@ -6658,18 +6666,31 @@ body stay." (define-key map "o" (cons "my open issues" #'pearl-list-issues)) (define-key map "p" (cons "by project" #'pearl-list-issues-by-project)) (define-key map "f" (cons "build a filter" #'pearl-list-issues-filtered)) - (define-key map "v" (cons "run Linear view" #'pearl-run-linear-view)) + map) + "Pearl issue-fetch commands; a sub-keymap of `pearl-prefix-map'. +Issue sources only -- pick a source, your open issues, a project, or an +ad-hoc filter. View-movement commands (run / publish / save-locally) live +in `pearl-views-map' so this group stays one concern.") + +(defvar pearl-views-map + (let ((map (make-sparse-keymap))) (define-key map "l" (cons "run local view" #'pearl-run-local-view)) + (define-key map "L" (cons "run Linear view" #'pearl-run-linear-view)) + (define-key map "c" (cons "create local view" #'pearl-create-local-view)) + (define-key map "e" (cons "edit local view" #'pearl-edit-local-view)) + (define-key map "k" (cons "delete local view" #'pearl-delete-local-view)) (define-key map "u" (cons "publish local view" #'pearl-publish-local-view)) + (define-key map "U" (cons "publish current view" #'pearl-publish-current-view)) (define-key map "d" (cons "save Linear view locally" #'pearl-save-linear-view-locally)) - (define-key map "P" (cons "publish current view" #'pearl-publish-current-view)) + ;; `D' (set default view) is reserved for the not-yet-built + ;; `pearl-set-default-view'. Left intentionally unbound so the slot is + ;; free when that command lands; do not bind another command to it. map) - "Pearl fetch commands; a sub-keymap of `pearl-prefix-map'. -The view-movement keys are `l' run local view, `v' run Linear view, `u' -publish a local view up to Linear, and `d' save a Linear view locally -\(added with copy-down). `p' stays `by project', so publish takes `u' -\(up) and save-locally takes `d' (down), mirroring the copy-up/copy-down -lifecycle.") + "Pearl view commands; a sub-keymap of `pearl-prefix-map'. +Lowercase `l' / capital `L' run a local / Linear view; `c' / `e' / `k' +create / edit / delete a local view. `u' / `U' publish a named / the +current local view up to Linear; `d' saves a Linear view down into a new +local view. Capital `D' is reserved for set-default-view.") (defvar pearl-edit-map (let ((map (make-sparse-keymap))) @@ -6678,25 +6699,37 @@ lifecycle.") (define-key map "a" (cons "edit assignee" #'pearl-edit-assignee)) (define-key map "l" (cons "edit labels" #'pearl-edit-labels)) (define-key map "c" (cons "edit comment" #'pearl-edit-current-comment)) - (define-key map "v" (cons "edit local view" #'pearl-edit-local-view)) map) - "Pearl edit commands; a sub-keymap of `pearl-prefix-map'.") + "Pearl issue-field edit commands; a sub-keymap of `pearl-prefix-map'. +Editing a local view lives in `pearl-views-map' (`v e').") (defvar pearl-create-map (let ((map (make-sparse-keymap))) (define-key map "t" (cons "create issue" #'pearl-create-issue)) (define-key map "c" (cons "create comment" #'pearl-create-comment)) - (define-key map "v" (cons "create local view" #'pearl-create-local-view)) map) - "Pearl create commands; a sub-keymap of `pearl-prefix-map'.") + "Pearl create commands; a sub-keymap of `pearl-prefix-map'. +Creating a local view lives in `pearl-views-map' (`v c').") (defvar pearl-delete-map (let ((map (make-sparse-keymap))) (define-key map "t" (cons "delete issue" #'pearl-delete-current-issue)) (define-key map "c" (cons "delete comment" #'pearl-delete-current-comment)) - (define-key map "v" (cons "delete local view" #'pearl-delete-local-view)) map) - "Pearl delete commands; a sub-keymap of `pearl-prefix-map'.") + "Pearl delete commands; a sub-keymap of `pearl-prefix-map'. +Deleting a local view lives in `pearl-views-map' (`v k').") + +(defvar pearl-workspace-map + (let ((map (make-sparse-keymap))) + (define-key map "a" (cons "switch account" #'pearl-switch-account)) + (define-key map "t" (cons "test connection" #'pearl-test-connection)) + (define-key map "c" (cons "check setup" #'pearl-check-setup)) + (define-key map "!" (cons "toggle debug" #'pearl-toggle-debug)) + (define-key map "x" (cons "clear cache" #'pearl-clear-cache)) + map) + "Pearl workspace and setup commands; a sub-keymap of `pearl-prefix-map'. +`a' switches the active Linear account; the rest are connection / setup +diagnostics.") (defvar pearl-open-map (let ((map (make-sparse-keymap))) @@ -6721,19 +6754,23 @@ lifecycle.") (define-key map "S" (cons "save all" #'pearl-save-all)) (define-key map "d" (cons "edit description" #'pearl-edit-description)) (define-key map "m" (cons "menu" #'pearl-menu)) - ;; Category sub-maps -- two keys; each prefix names an action verb. + ;; Category sub-maps -- two keys; each prefix names an action verb or noun. (define-key map "f" (cons "+fetch" pearl-fetch-map)) + (define-key map "v" (cons "+views" pearl-views-map)) (define-key map "e" (cons "+edit" pearl-edit-map)) (define-key map "c" (cons "+create" pearl-create-map)) (define-key map "k" (cons "+delete" pearl-delete-map)) (define-key map "o" (cons "+open" pearl-open-map)) (define-key map "y" (cons "+copy" pearl-copy-map)) + (define-key map "w" (cons "+workspace" pearl-workspace-map)) map) "Pearl command prefix keymap. -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).") +Hot-path commands are bound directly; the rest live under sub-maps +\(fetch / views / edit / create / delete / open / copy / workspace). Fetch is +issue sources only; views have their own group; workspace holds account +switching and setup. `pearl-mode' binds this under `pearl-keymap-prefix' +inside Pearl buffers; you can also bind it globally (see the Commentary +above).") (defcustom pearl-keymap-prefix "C-; L" "Prefix key that `pearl-mode' binds to `pearl-prefix-map'. diff --git a/tests/test-pearl-keymap.el b/tests/test-pearl-keymap.el index 89fe349..643005b 100644 --- a/tests/test-pearl-keymap.el +++ b/tests/test-pearl-keymap.el @@ -20,10 +20,11 @@ ;;; Commentary: ;; Tests for `pearl-prefix-map' and `pearl-mode'. Hot-path commands bind -;; directly under the prefix; the rest live in category sub-maps (fetch / edit -;; / new / delete / url). The common commands appear in both places. -;; `pearl-mode' makes the map live inside Pearl buffers under -;; `pearl-keymap-prefix'. +;; directly under the prefix; the rest live in category sub-maps. Fetch +;; (`f') is issue-fetching only; views have their own group (`v'); workspace +;; / setup commands live under `w'. The common commands appear both as a +;; direct hot key and inside their group. `pearl-mode' makes the map live +;; inside Pearl buffers under `pearl-keymap-prefix'. ;;; Code: @@ -57,48 +58,70 @@ The lowercase `c' is no longer a direct command -- it is the create prefix." (should (eq 'pearl-menu (lookup-key pearl-prefix-map (kbd "m"))))) (ert-deftest test-pearl-prefix-map-sub-prefixes-are-verbs () - "Each category key is a sub-prefix keymap named for an action verb." + "Each category key is a sub-prefix keymap named for an action verb or noun." (should (keymapp (lookup-key pearl-prefix-map (kbd "f")))) ; fetch + (should (keymapp (lookup-key pearl-prefix-map (kbd "v")))) ; views (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 + (should (keymapp (lookup-key pearl-prefix-map (kbd "y")))) ; copy + (should (keymapp (lookup-key pearl-prefix-map (kbd "w"))))) ; workspace -(ert-deftest test-pearl-prefix-map-fetch-group () - "The fetch group resolves each source and view-run command." +(ert-deftest test-pearl-prefix-map-fetch-group-is-issue-sources-only () + "The fetch group resolves the four issue-source commands and nothing else. +View-movement commands moved to the dedicated views group, so the old +`f l' / `f v' / `f u' / `f d' / `f P' chains are gone." (should (eq 'pearl-pick-source (lookup-key pearl-prefix-map (kbd "f s")))) (should (eq 'pearl-list-issues (lookup-key pearl-prefix-map (kbd "f o")))) (should (eq 'pearl-list-issues-by-project (lookup-key pearl-prefix-map (kbd "f p")))) (should (eq 'pearl-list-issues-filtered (lookup-key pearl-prefix-map (kbd "f f")))) - (should (eq 'pearl-run-linear-view (lookup-key pearl-prefix-map (kbd "f v")))) - (should (eq 'pearl-run-local-view (lookup-key pearl-prefix-map (kbd "f l")))) - (should (eq 'pearl-publish-local-view - (lookup-key pearl-prefix-map (kbd "f u")))) - (should (eq 'pearl-save-linear-view-locally - (lookup-key pearl-prefix-map (kbd "f d")))) - (should (eq 'pearl-publish-current-view - (lookup-key pearl-prefix-map (kbd "f P"))))) + ;; the view commands are no longer under fetch + (should-not (lookup-key pearl-prefix-map (kbd "f l"))) + (should-not (lookup-key pearl-prefix-map (kbd "f v"))) + (should-not (lookup-key pearl-prefix-map (kbd "f u"))) + (should-not (lookup-key pearl-prefix-map (kbd "f d"))) + (should-not (lookup-key pearl-prefix-map (kbd "f P")))) + +(ert-deftest test-pearl-prefix-map-views-group () + "The dedicated views group gathers run / create / edit / delete / copy-direction. +Lowercase `l'/`L' run local / Linear; `c'/`e'/`k' create / edit / delete a +local view; `u'/`U' publish (copy up); `d' saves a Linear view locally +\(copy down)." + (should (eq 'pearl-run-local-view (lookup-key pearl-prefix-map (kbd "v l")))) + (should (eq 'pearl-run-linear-view (lookup-key pearl-prefix-map (kbd "v L")))) + (should (eq 'pearl-create-local-view (lookup-key pearl-prefix-map (kbd "v c")))) + (should (eq 'pearl-edit-local-view (lookup-key pearl-prefix-map (kbd "v e")))) + (should (eq 'pearl-delete-local-view (lookup-key pearl-prefix-map (kbd "v k")))) + (should (eq 'pearl-publish-local-view (lookup-key pearl-prefix-map (kbd "v u")))) + (should (eq 'pearl-publish-current-view (lookup-key pearl-prefix-map (kbd "v U")))) + (should (eq 'pearl-save-linear-view-locally (lookup-key pearl-prefix-map (kbd "v d"))))) + +(ert-deftest test-pearl-prefix-map-views-default-slot-reserved () + "`v D' is reserved for the not-yet-built set-default-view; it stays unbound." + (should-not (lookup-key pearl-prefix-map (kbd "v D")))) (ert-deftest test-pearl-prefix-map-edit-group () - "The edit group resolves each field-edit command." + "The edit group resolves field-edit commands only -- no view edit anymore." (should (eq 'pearl-edit-description (lookup-key pearl-prefix-map (kbd "e d")))) (should (eq 'pearl-edit-state (lookup-key pearl-prefix-map (kbd "e s")))) (should (eq 'pearl-edit-assignee (lookup-key pearl-prefix-map (kbd "e a")))) (should (eq 'pearl-edit-labels (lookup-key pearl-prefix-map (kbd "e l")))) (should (eq 'pearl-edit-current-comment (lookup-key pearl-prefix-map (kbd "e c")))) - (should (eq 'pearl-edit-local-view (lookup-key pearl-prefix-map (kbd "e v")))) + ;; view-edit moved to the views group + (should-not (lookup-key pearl-prefix-map (kbd "e v"))) ;; 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-create-and-delete-groups () - "Create and delete groups resolve issue/comment plus the local-view slot." + "Create and delete groups resolve issue / comment only -- views moved out." (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-create-local-view (lookup-key pearl-prefix-map (kbd "c v")))) (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")))) - (should (eq 'pearl-delete-local-view (lookup-key pearl-prefix-map (kbd "k v"))))) + ;; view create / delete moved to the views group + (should-not (lookup-key pearl-prefix-map (kbd "c v"))) + (should-not (lookup-key pearl-prefix-map (kbd "k v")))) (ert-deftest test-pearl-prefix-map-open-and-copy-groups () "The open group resolves the two open actions; copy resolves copy-url." @@ -106,6 +129,15 @@ The lowercase `c' is no longer a direct command -- it is the create prefix." (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-workspace-group () + "The workspace group binds account switching plus the setup commands. +`pearl-switch-account' was previously unbound on every surface." + (should (eq 'pearl-switch-account (lookup-key pearl-prefix-map (kbd "w a")))) + (should (eq 'pearl-test-connection (lookup-key pearl-prefix-map (kbd "w t")))) + (should (eq 'pearl-check-setup (lookup-key pearl-prefix-map (kbd "w c")))) + (should (eq 'pearl-toggle-debug (lookup-key pearl-prefix-map (kbd "w !")))) + (should (eq 'pearl-clear-cache (lookup-key pearl-prefix-map (kbd "w x"))))) + (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 @@ -125,8 +157,14 @@ The lowercase `c' is no longer a direct command -- it is the create prefix." (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= "run local view" + (test-pearl-keymap--label-of pearl-views-map 'pearl-run-local-view))) + (should (string= "switch account" + (test-pearl-keymap--label-of pearl-workspace-map 'pearl-switch-account))) (should (string= "+edit" (test-pearl-keymap--label-of pearl-prefix-map pearl-edit-map))) - (should (string= "+open" (test-pearl-keymap--label-of pearl-prefix-map pearl-open-map)))) + (should (string= "+views" (test-pearl-keymap--label-of pearl-prefix-map pearl-views-map))) + (should (string= "+workspace" + (test-pearl-keymap--label-of pearl-prefix-map pearl-workspace-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 9c97384..8e18274 100644 --- a/tests/test-pearl-menu.el +++ b/tests/test-pearl-menu.el @@ -69,7 +69,11 @@ menu entry that still points at it fails here." pearl-edit-state pearl-create-comment pearl-create-issue - pearl-delete-current-issue)) + pearl-delete-current-issue + ;; previously missing from the menu -- added in the + ;; keybinding reconciliation so both surfaces agree + pearl-switch-account + pearl-copy-issue-url)) (should (memq expected cmds))))) (provide 'test-pearl-menu) -- cgit v1.2.3