aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/linear-config.el200
-rw-r--r--tests/test-linear-config.el86
2 files changed, 30 insertions, 256 deletions
diff --git a/modules/linear-config.el b/modules/linear-config.el
index 85191828..e4b6599d 100644
--- a/modules/linear-config.el
+++ b/modules/linear-config.el
@@ -5,187 +5,47 @@
;;
;; Layer: 3 (Domain Workflow).
;; Category: D/P.
-;; Load shape: eager.
-;; Eager reason: none; Linear integration commands, a command-loaded deferral
-;; candidate.
+;; Load shape: deferred (command-loaded).
;; Top-level side effects: package configuration via use-package.
-;; Runtime requires: system-lib.
-;; Direct test load: yes.
+;; Runtime requires: none.
+;; Direct test load: no.
;;
-;; Wires the local pearl checkout (~/code/pearl) into the config,
-;; pointed at DeepSat's Linear workspace.
+;; Vanilla pearl setup, deliberately kept to exactly what pearl's README
+;; documents for a first-time install — no custom keymap, no team default, no
+;; lazy-key advice — so it can be dogfooded as the real out-of-box experience.
+;; The only deviation from the README is loading from the local checkout
+;; (~/code/pearl) instead of a package archive.
;;
-;; Authentication:
-;; The Linear personal API key is read from authinfo.gpg, never plaintext.
-;; Add an entry like:
-;; machine api.linear.app login apikey password lin_api_YOURKEYHERE
-;; Generate the key in Linear: Settings -> Security & access -> Personal API
-;; keys. The key is loaded lazily on first use, so there is no GPG prompt at
-;; startup.
+;; pearl owns its own keymap. `pearl-mode' turns on automatically in any buffer
+;; pearl renders (it carries a `#+LINEAR-SOURCE' header) and binds the whole
+;; command surface under `pearl-keymap-prefix' (default "C-; L"). This config
+;; binds no global key, so from a non-Linear buffer reach pearl with `M-x'
+;; (e.g. `M-x pearl-list-issues' or `M-x pearl-menu'); inside a Linear buffer
+;; everything is live under C-; L.
;;
-;; The default team is DeepSat's Software Engineering team (the SE-* issues), so
-;; new issues land there unless another team is chosen. The synced issues file
-;; lives at data/linear.org inside emacs home, next to the calendar-sync output.
-;;
-;; Keybindings (C-; L prefix):
-;; Lists/views: l list p by project f filtered q saved query v view
-;; Refresh: g current view r current issue
-;; Issue: o open in browser n new D delete at point
-;; Edit (C-; L e): a assignee s state p priority b labels c comment
-;; Sync: s enable org sync S disable u push issue U push title
-;; View: V open current view in Linear
-;; Maintenance: t test connection ? check setup k clear cache d debug
+;; Authentication: the Linear personal API key is read from authinfo.gpg. Add:
+;; machine api.linear.app login apikey password lin_api_YOURKEYHERE
+;; Generate it in Linear: Settings -> Security & access -> Personal API keys.
;;; Code:
-(require 'system-lib) ;; provides cj/auth-source-secret-value
-
-;; Owned by pearl, which loads lazily via :load-path below.
-(defvar pearl-api-key)
-(defvar pearl-default-team-id)
-(defvar pearl-org-file-path)
-(declare-function pearl--graphql-request-async "pearl")
-
-(defconst cj/linear-team-id "9fca2cf6-390c-4102-a9ff-f94a4ed823c5"
- "Linear team id for DeepSat's Software Engineering team (the SE-* issues).")
-
-(defun cj/linear--ensure-api-key ()
- "Load the Linear API key from authinfo.gpg into `pearl-api-key' if unset.
-Looks up host \"api.linear.app\". This is a no-op once the key is set, so the
-GPG prompt fires at most once per session and only when Linear is actually used."
- (unless pearl-api-key
- (setq pearl-api-key (cj/auth-source-secret-value "api.linear.app"))))
-
-(defun cj/linear--ensure-key-before (&rest _)
- "Advice: load the Linear API key before a GraphQL request runs.
-Named (not a lambda) so the advice is idempotent across reloads and removable."
- (cj/linear--ensure-api-key))
-
-(defun cj/linear--install-key-advice ()
- "Install the lazy API-key loader on every entry point that needs the key.
-The GraphQL request funnels all real operations. `pearl-check-setup'
-reads `pearl-api-key' directly without making a request, so it needs the
-loader too — otherwise it reports \"not set\" on a fresh session before the key
-has ever been fetched."
- (advice-add 'pearl--graphql-request-async :before
- #'cj/linear--ensure-key-before)
- (advice-add 'pearl-check-setup :before
- #'cj/linear--ensure-key-before))
-
(use-package pearl
:ensure nil ;; local checkout, not from an archive
:load-path "~/code/pearl"
- :defer t
- :commands (pearl-list-issues
- pearl-list-issues-by-project
- pearl-list-issues-filtered
- pearl-run-saved-query
- pearl-run-view
- pearl-refresh-current-view
- pearl-refresh-current-issue
- pearl-open-current-issue
- pearl-open-current-view-in-linear
- pearl-new-issue
- pearl-delete-current-issue
- pearl-add-comment
- pearl-set-assignee
- pearl-set-state
- pearl-set-priority
- pearl-set-labels
- pearl-sync-current-issue
- pearl-sync-current-issue-title
- pearl-enable-org-sync
- pearl-disable-org-sync
- pearl-clear-cache
- pearl-toggle-debug
- pearl-load-api-key-from-env
- pearl-test-connection
- pearl-check-setup)
+ :commands (pearl-menu pearl-list-issues pearl-create-issue pearl-run-linear-view)
+ :custom
+ (pearl-org-file-path (expand-file-name "gtd/linear.org" org-directory))
+ ;; Optional defaults — uncomment and fill in to skip the prompts. Set them
+ ;; HERE, at init level, not via M-x pearl-set-default-view /
+ ;; pearl-set-default-team: those persist through `customize-save-variable',
+ ;; and this config redirects `custom-file' to a throwaway temp file
+ ;; (system-defaults.el), so a setter's value is discarded on the next
+ ;; restart. These :custom lines re-apply on every startup instead.
+ ;; (pearl-default-view "My active work") ;; the local view `C-; L l' opens
+ ;; (pearl-default-team-id "9fca2cf6-390c-4102-a9ff-f94a4ed823c5") ;; DeepSat SE; skips the team prompt on create / by-project
:config
- (setq pearl-default-team-id cj/linear-team-id)
- ;; Keep the synced org file inside emacs home, next to the calendar-sync
- ;; output (gcal.org / pcal.org / dcal.org). Without this it falls back to
- ;; `org-directory'/gtd/linear.org and silently creates a stray ~/org tree.
- (setq pearl-org-file-path
- (expand-file-name "data/linear.org" user-emacs-directory))
- ;; Load the key lazily before any operation that reads it — both the GraphQL
- ;; request and the check-setup diagnostic. Retries if the key was added to
- ;; authinfo after a first (failed) attempt this session.
- (cj/linear--install-key-advice))
-
-;; ------------------------------ Keybindings ----------------------------------
-
-(defvar cj/linear-edit-keymap (make-sparse-keymap)
- "Keymap for editing the Linear issue at point, under C-; L e.")
-
-(defvar cj/linear-keymap (make-sparse-keymap)
- "Keymap for Linear commands under C-; L.")
-
-(global-set-key (kbd "C-; L") cj/linear-keymap)
-
-;; Lists and views.
-(define-key cj/linear-keymap (kbd "l") #'pearl-list-issues)
-(define-key cj/linear-keymap (kbd "p") #'pearl-list-issues-by-project)
-(define-key cj/linear-keymap (kbd "f") #'pearl-list-issues-filtered)
-(define-key cj/linear-keymap (kbd "q") #'pearl-run-saved-query)
-(define-key cj/linear-keymap (kbd "v") #'pearl-run-view)
-;; Refresh.
-(define-key cj/linear-keymap (kbd "g") #'pearl-refresh-current-view)
-(define-key cj/linear-keymap (kbd "r") #'pearl-refresh-current-issue)
-;; Issue actions.
-(define-key cj/linear-keymap (kbd "o") #'pearl-open-current-issue)
-(define-key cj/linear-keymap (kbd "V") #'pearl-open-current-view-in-linear)
-(define-key cj/linear-keymap (kbd "n") #'pearl-new-issue)
-(define-key cj/linear-keymap (kbd "D") #'pearl-delete-current-issue)
-;; Sync.
-(define-key cj/linear-keymap (kbd "s") #'pearl-enable-org-sync)
-(define-key cj/linear-keymap (kbd "S") #'pearl-disable-org-sync)
-(define-key cj/linear-keymap (kbd "u") #'pearl-sync-current-issue)
-(define-key cj/linear-keymap (kbd "U") #'pearl-sync-current-issue-title)
-;; Maintenance.
-(define-key cj/linear-keymap (kbd "t") #'pearl-test-connection)
-(define-key cj/linear-keymap (kbd "?") #'pearl-check-setup)
-(define-key cj/linear-keymap (kbd "k") #'pearl-clear-cache)
-(define-key cj/linear-keymap (kbd "d") #'pearl-toggle-debug)
-;; Edit-issue sub-prefix.
-(define-key cj/linear-keymap (kbd "e") cj/linear-edit-keymap)
-(define-key cj/linear-edit-keymap (kbd "a") #'pearl-set-assignee)
-(define-key cj/linear-edit-keymap (kbd "s") #'pearl-set-state)
-(define-key cj/linear-edit-keymap (kbd "p") #'pearl-set-priority)
-(define-key cj/linear-edit-keymap (kbd "b") #'pearl-set-labels)
-(define-key cj/linear-edit-keymap (kbd "c") #'pearl-add-comment)
-
-;; Register which-key labels lazily so this module's require doesn't depend on
-;; which-key being loaded. Same pattern as the other client modules.
-(with-eval-after-load 'which-key
- (which-key-add-keymap-based-replacements cj/linear-keymap
- "" "linear menu"
- "l" "list issues"
- "p" "issues by project"
- "f" "filtered issues"
- "q" "saved query"
- "v" "run view"
- "g" "refresh view"
- "r" "refresh issue"
- "o" "open issue in browser"
- "V" "open view in linear"
- "n" "new issue"
- "D" "delete issue"
- "s" "enable org sync"
- "S" "disable org sync"
- "u" "push issue"
- "U" "push issue title"
- "t" "test connection"
- "?" "check setup"
- "k" "clear cache"
- "d" "toggle debug"
- "e" "edit issue")
- (which-key-add-keymap-based-replacements cj/linear-edit-keymap
- "a" "set assignee"
- "s" "set state"
- "p" "set priority"
- "b" "set labels"
- "c" "add comment"))
+ (setq pearl-api-key
+ (auth-source-pick-first-password :host "api.linear.app")))
(provide 'linear-config)
;;; linear-config.el ends here
diff --git a/tests/test-linear-config.el b/tests/test-linear-config.el
deleted file mode 100644
index 97d94bc4..00000000
--- a/tests/test-linear-config.el
+++ /dev/null
@@ -1,86 +0,0 @@
-;;; test-linear-config.el --- Tests for linear-config.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Covers the lazy API-key loader and the keybinding wiring. pearl
-;; itself is never loaded here (it's a deferred :load-path package), so
-;; `pearl-api-key' is declared special below to make the dynamic
-;; let-bindings reach `cj/linear--ensure-api-key'. `cj/auth-source-secret-value'
-;; is stubbed — no authinfo.gpg / GPG access in the tests.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-(require 'linear-config)
-
-;; linear-config declares this with a bare (defvar pearl-api-key), which
-;; is only file-local; declare it special here so the let-bindings are dynamic.
-(defvar pearl-api-key nil)
-
-(ert-deftest test-linear-ensure-api-key-loads-when-unset ()
- "Normal: an unset key is loaded from auth-source."
- (let ((pearl-api-key nil))
- (cl-letf (((symbol-function 'cj/auth-source-secret-value)
- (lambda (&rest _) "lin_api_test")))
- (cj/linear--ensure-api-key)
- (should (equal pearl-api-key "lin_api_test")))))
-
-(ert-deftest test-linear-ensure-api-key-keeps-existing ()
- "Boundary: an already-set key is neither overwritten nor re-fetched."
- (let ((pearl-api-key "already-set") (fetched nil))
- (cl-letf (((symbol-function 'cj/auth-source-secret-value)
- (lambda (&rest _) (setq fetched t) "other")))
- (cj/linear--ensure-api-key)
- (should (equal pearl-api-key "already-set"))
- (should-not fetched))))
-
-(ert-deftest test-linear-ensure-api-key-nil-when-absent ()
- "Boundary: a missing authinfo entry leaves the key nil without error."
- (let ((pearl-api-key nil))
- (cl-letf (((symbol-function 'cj/auth-source-secret-value) (lambda (&rest _) nil)))
- (cj/linear--ensure-api-key)
- (should-not pearl-api-key))))
-
-(ert-deftest test-linear-install-key-advice-loads-before-check-setup ()
- "Error-regression: `pearl-check-setup' loads the key before reading it.
-The lazy loader originally only advised the GraphQL request entry point, so
-`check-setup' — which reads `pearl-api-key' directly without making a
-request — falsely reported \"not set\" on a fresh session."
- (let ((pearl-api-key nil)
- (key-at-read :unread))
- (cl-letf (((symbol-function 'cj/auth-source-secret-value)
- (lambda (&rest _) "lin_api_test"))
- ((symbol-function 'pearl--graphql-request-async)
- (lambda (&rest _) nil))
- ((symbol-function 'pearl-check-setup)
- (lambda () (setq key-at-read pearl-api-key))))
- (cj/linear--install-key-advice)
- (unwind-protect
- (progn
- (pearl-check-setup)
- (should (equal key-at-read "lin_api_test")))
- (advice-remove 'pearl--graphql-request-async
- #'cj/linear--ensure-key-before)
- (advice-remove 'pearl-check-setup
- #'cj/linear--ensure-key-before)))))
-
-(ert-deftest test-linear-keymap-bound-under-prefix ()
- "Smoke: C-; L holds the linear keymap and the entry commands are bound."
- (should (keymapp cj/linear-keymap))
- (should (eq (keymap-lookup (current-global-map) "C-; L") cj/linear-keymap))
- (should (eq (keymap-lookup cj/linear-keymap "l") #'pearl-list-issues))
- (should (eq (keymap-lookup cj/linear-keymap "n") #'pearl-new-issue))
- ;; commands added in the package rework
- (should (eq (keymap-lookup cj/linear-keymap "f") #'pearl-list-issues-filtered))
- (should (eq (keymap-lookup cj/linear-keymap "v") #'pearl-run-view))
- (should (eq (keymap-lookup cj/linear-keymap "o") #'pearl-open-current-issue)))
-
-(ert-deftest test-linear-edit-submap-bound ()
- "Smoke: C-; L e holds the edit-issue sub-keymap with field commands."
- (should (keymapp cj/linear-edit-keymap))
- (should (eq (keymap-lookup cj/linear-keymap "e") cj/linear-edit-keymap))
- (should (eq (keymap-lookup cj/linear-edit-keymap "a") #'pearl-set-assignee))
- (should (eq (keymap-lookup cj/linear-edit-keymap "s") #'pearl-set-state)))
-
-(provide 'test-linear-config)
-;;; test-linear-config.el ends here