From 835c2d3a4bd3f695889911edd7a9681c38ff8581 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 24 May 2026 00:03:39 -0500 Subject: feat(dashboard): add a Linear launcher and group the navigator by row sizes I added a Linear entry to the launcher table, keyed l, with the nf-oct-issue_tracks octicon, opening the issue list via linear-emacs-list-issues. That makes 13 launchers, which no longer divides into the old rigid 4-per-row grid. So I replaced the fixed chunk-by-4 with an explicit cj/dashboard--row-sizes (4 4 3 2) and reordered the table so Telegram comes before Slack, putting Slack and Linear together on the last row. The button shape moved into cj/dashboard--navigator-button, shared by the grouped loop and a fallback row for any launchers the sizes don't cover. A test pins the row sizes to the launcher count so they can't drift. --- modules/dashboard-config.el | 40 +++++++++++++++++++++----------- tests/test-dashboard-config-launchers.el | 36 ++++++++++++++++++---------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/modules/dashboard-config.el b/modules/dashboard-config.el index 74c32bd5..cb853d1d 100644 --- a/modules/dashboard-config.el +++ b/modules/dashboard-config.el @@ -75,25 +75,37 @@ Adjust this if the title doesn't appear centered under the banner image.") (list "m" #'nerd-icons-mdicon "nf-md-music" "Music" "EMMS Music Player" (lambda () (cj/music-playlist-toggle) (cj/music-playlist-load))) (list "e" #'nerd-icons-faicon "nf-fa-envelope" "Email" "Mu4e Email Client" (lambda () (mu4e))) (list "i" #'nerd-icons-faicon "nf-fa-comments" "IRC" "Emacs Relay Chat" (lambda () (cj/erc-switch-to-buffer-with-completion))) + (list "g" #'nerd-icons-faicon "nf-fa-telegram" "Telegram" "Telega Telegram Client" (lambda () (cj/telega))) (list "s" #'nerd-icons-faicon "nf-fa-slack" "Slack" "Slack Client" (lambda () (cj/slack-start))) - (list "g" #'nerd-icons-faicon "nf-fa-telegram" "Telegram" "Telega Telegram Client" (lambda () (cj/telega)))) + (list "l" #'nerd-icons-octicon "nf-oct-issue_tracks" "Linear" "Linear Issue Tracker" (lambda () (linear-emacs-list-issues)))) "Dashboard launcher table: (KEY ICON-FN ICON-NAME LABEL TOOLTIP ACTION). Drives both `dashboard-navigator-buttons' and the dashboard-mode-map keys.") +(defconst cj/dashboard--row-sizes '(4 4 3 2) + "Navigator row lengths. Must sum to the number of `cj/dashboard--launchers'. +The last row groups Slack and Linear together.") + +(defun cj/dashboard--navigator-button (l) + "Build a `dashboard-navigator-buttons' entry from launcher L." + (let ((icon-fn (nth 1 l)) (icon-name (nth 2 l)) + (label (nth 3 l)) (tooltip (nth 4 l)) (action (nth 5 l))) + (list (funcall icon-fn icon-name) label tooltip + (lambda (&rest _) (funcall action)) nil " " ""))) + (defun cj/dashboard--navigator-rows () - "Build `dashboard-navigator-buttons' rows from `cj/dashboard--launchers'. -Chunks the launchers four per row and maps each to a navigator button." - (let (rows row) - (dolist (l cj/dashboard--launchers) - (let ((icon-fn (nth 1 l)) (icon-name (nth 2 l)) - (label (nth 3 l)) (tooltip (nth 4 l)) (action (nth 5 l))) - (push (list (funcall icon-fn icon-name) label tooltip - (lambda (&rest _) (funcall action)) nil " " "") - row)) - (when (= (length row) 4) - (push (nreverse row) rows) - (setq row nil))) - (when row (push (nreverse row) rows)) + "Build navigator rows from `cj/dashboard--launchers', grouped per +`cj/dashboard--row-sizes'. Any launchers beyond the declared row sizes form a +final row, so a newly added launcher still shows up even if the sizes weren't +updated." + (let ((launchers cj/dashboard--launchers) rows) + (dolist (size cj/dashboard--row-sizes) + (let (row) + (dotimes (_ size) + (when launchers + (push (cj/dashboard--navigator-button (pop launchers)) row))) + (when row (push (nreverse row) rows)))) + (when launchers + (push (mapcar #'cj/dashboard--navigator-button launchers) rows)) (nreverse rows))) (defun cj/dashboard--bind-launchers (map) diff --git a/tests/test-dashboard-config-launchers.el b/tests/test-dashboard-config-launchers.el index 633d6612..f6dfb042 100644 --- a/tests/test-dashboard-config-launchers.el +++ b/tests/test-dashboard-config-launchers.el @@ -25,33 +25,43 @@ (require 'dashboard-config) -(defconst test-dash--keys '("c" "d" "t" "a" "r" "b" "f" "m" "e" "i" "s" "g")) +(defconst test-dash--keys '("c" "d" "t" "a" "r" "b" "f" "m" "e" "i" "g" "s" "l")) ;; ----------------------------- launcher table -------------------------------- (ert-deftest test-dashboard-launchers-keys-in-order () - "Normal: 12 launchers with the expected keys in display order." - (should (= 12 (length cj/dashboard--launchers))) + "Normal: 13 launchers with the expected keys in display order." + (should (= 13 (length cj/dashboard--launchers))) (should (equal test-dash--keys (mapcar (lambda (l) (nth 0 l)) cj/dashboard--launchers)))) (ert-deftest test-dashboard-launchers-labels-in-order () - "Normal: labels in display order." + "Normal: labels in display order (Telegram and Slack reordered so Slack sits +next to Linear on the last navigator row)." (should (equal '("Code" "Files" "Terminal" "Agenda" "Feeds" "Books" - "Flashcards" "Music" "Email" "IRC" "Slack" "Telegram") + "Flashcards" "Music" "Email" "IRC" "Telegram" "Slack" "Linear") (mapcar (lambda (l) (nth 3 l)) cj/dashboard--launchers)))) +(ert-deftest test-dashboard-row-sizes-cover-all-launchers () + "Normal: the navigator row sizes sum to the launcher count." + (should (= (length cj/dashboard--launchers) + (apply #'+ cj/dashboard--row-sizes)))) + ;; --------------------------- navigator rows ---------------------------------- -(ert-deftest test-dashboard-navigator-rows-three-rows-of-four () - "Normal: navigator derives 3 rows of 4, with the right labels and button shape." +(ert-deftest test-dashboard-navigator-rows-grouped-4-4-3-2 () + "Normal: navigator derives rows per `cj/dashboard--row-sizes' (4 4 3 2), with +Slack and Linear sharing the last row." (cl-letf (((symbol-function 'nerd-icons-faicon) (lambda (n &rest _) (concat "I:" n))) ((symbol-function 'nerd-icons-devicon) (lambda (n &rest _) (concat "I:" n))) - ((symbol-function 'nerd-icons-mdicon) (lambda (n &rest _) (concat "I:" n)))) + ((symbol-function 'nerd-icons-mdicon) (lambda (n &rest _) (concat "I:" n))) + ((symbol-function 'nerd-icons-octicon) (lambda (n &rest _) (concat "I:" n)))) (let ((rows (cj/dashboard--navigator-rows))) - (should (= 3 (length rows))) - (should (cl-every (lambda (r) (= 4 (length r))) rows)) + (should (= 4 (length rows))) + (should (equal '(4 4 3 2) (mapcar #'length rows))) (should (equal '("Code" "Files" "Terminal" "Agenda") (mapcar (lambda (b) (nth 1 b)) (nth 0 rows)))) + (should (equal '("Slack" "Linear") + (mapcar (lambda (b) (nth 1 b)) (nth 3 rows)))) (let ((btn (car (car rows)))) ; (icon label tooltip action nil " " "") (should (string= "I:nf-fa-code" (nth 0 btn))) (should (string= "Code" (nth 1 btn))) @@ -83,15 +93,17 @@ ((symbol-function 'mu4e) (lambda (&rest _) (push 'email calls))) ((symbol-function 'cj/erc-switch-to-buffer-with-completion) (lambda (&rest _) (push 'irc calls))) ((symbol-function 'cj/slack-start) (lambda (&rest _) (push 'slack calls))) - ((symbol-function 'cj/telega) (lambda (&rest _) (push 'tg calls)))) + ((symbol-function 'cj/telega) (lambda (&rest _) (push 'tg calls))) + ((symbol-function 'linear-emacs-list-issues) (lambda (&rest _) (push 'linear calls)))) (cj/dashboard--bind-launchers map) (dolist (key test-dash--keys) (call-interactively (keymap-lookup map key))) (should (memq 'code calls)) (should (memq 'tg calls)) + (should (memq 'linear calls)) (should (memq 'm-toggle calls)) (should (memq 'm-load calls)) - (should (= 13 (length calls)))))) ; 12 keys, Music fires two + (should (= 14 (length calls)))))) ; 13 keys, Music fires two (provide 'test-dashboard-config-launchers) ;;; test-dashboard-config-launchers.el ends here -- cgit v1.2.3