aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-03 12:10:09 -0500
committerCraig Jennings <c@cjennings.net>2026-06-03 12:10:09 -0500
commit3c89a88e6f8a5ae75784568e866789804a7c0047 (patch)
tree93df423748dd29690763be5d0c674689b4bc3f60
parentcb73a13c85c9818979f21e5ca0fd6aee631fb61e (diff)
downloadpearl-3c89a88e6f8a5ae75784568e866789804a7c0047.tar.gz
pearl-3c89a88e6f8a5ae75784568e866789804a7c0047.zip
fix(views): slug the assignee @-tag from Linear's displayName handle
The assignee @-tag slugged the user's name field, which for a teammate who never set a display name in Linear is their full email. That produced tags like :@first_last_example_com: that wrap the heading's tag line across several lines. Linear's displayName is the short @-mention handle (e.g. "alice", "first.last"), consistent across users where name isn't. I normalize it onto the user as :display-name and slug the tag from it, falling back to name when it's absent. So an email-named teammate becomes :@first_last: and a handle-named one :@alice: instead of :@alice_smith:. The :LINEAR-ASSIGNEE-NAME drawer keeps the fuller name. Every issue query already selects displayName on the assignee, so no fetch change.
-rw-r--r--pearl.el23
-rw-r--r--tests/test-pearl-labels.el21
2 files changed, 37 insertions, 7 deletions
diff --git a/pearl.el b/pearl.el
index a3b93c0..bb91f35 100644
--- a/pearl.el
+++ b/pearl.el
@@ -1225,10 +1225,14 @@ CONNECTION is an alist with a `nodes' key holding a vector, a list, or nil."
(t nil))))
(defun pearl--normalize-user (raw)
- "Normalize a Linear user alist RAW to a plist, or nil when RAW is nil."
+ "Normalize a Linear user alist RAW to a plist, or nil when RAW is nil.
+`:display-name' is Linear's short @-mention handle (e.g. \"nerses\"), kept
+distinct from `:name' (which can be a full email for users who never set a
+display name) so the assignee @-tag can use the short form."
(when raw
(list :id (cdr (assoc 'id raw))
:name (or (cdr (assoc 'name raw)) (cdr (assoc 'displayName raw)))
+ :display-name (cdr (assoc 'displayName raw))
:email (cdr (assoc 'email raw)))))
(defun pearl--normalize-state (raw)
@@ -2497,13 +2501,18 @@ names that slugify to the same tag render it once at the first's position."
(defun pearl--assignee-tag (assignee)
"Return the @-prefixed Org tag for ASSIGNEE (a normalized user plist), or nil.
-Returns nil when ASSIGNEE is nil, `pearl-show-assignee' is off, or the name
-slugifies to empty. The slug reuses `pearl--label-name-to-tag'; the `@' prefix
-keeps the assignee tag in its own namespace, distinct from label tags, so the
-label-edit path can preserve it (see `pearl--set-heading-label-tags').
-Title-sync-safe: Org strips tags before the heading title is hashed."
+Returns nil when ASSIGNEE is nil, `pearl-show-assignee' is off, or the handle
+slugifies to empty. Slugs the short `:display-name' (Linear's @-mention handle,
+e.g. \"nerses\") and falls back to `:name' -- so a teammate whose `:name' is a
+full email gets @nerses, not @nerses_deepsat_com. The slug reuses
+`pearl--label-name-to-tag'; the `@' prefix keeps the assignee tag in its own
+namespace, distinct from label tags, so the label-edit path can preserve it (see
+`pearl--set-heading-label-tags'). Title-sync-safe: Org strips tags before the
+heading title is hashed."
(when (and pearl-show-assignee assignee)
- (let ((slug (pearl--label-name-to-tag (plist-get assignee :name))))
+ (let ((slug (pearl--label-name-to-tag
+ (or (plist-get assignee :display-name)
+ (plist-get assignee :name)))))
(unless (string-empty-p slug) (concat "@" slug)))))
(defun pearl--derive-todo-line (states)
diff --git a/tests/test-pearl-labels.el b/tests/test-pearl-labels.el
index 3872f31..94211b1 100644
--- a/tests/test-pearl-labels.el
+++ b/tests/test-pearl-labels.el
@@ -82,6 +82,27 @@
(should (string= "@eric" (pearl--assignee-tag '(:name "Eric"))))
(should (string= "@eric_smith" (pearl--assignee-tag '(:name "Eric Smith")))))
+(ert-deftest test-pearl-assignee-tag-prefers-display-name ()
+ "The tag uses Linear's short displayName (its @-mention handle), not the name
+field -- which is the full email for teammates who never set a display name."
+ (should (string= "@nerses"
+ (pearl--assignee-tag '(:name "Nerses Ohanyan" :display-name "nerses"))))
+ (should (string= "@vrezh_mikayelyan"
+ (pearl--assignee-tag '(:name "vrezh.mikayelyan@deepsat.com"
+ :display-name "vrezh.mikayelyan")))))
+
+(ert-deftest test-pearl-assignee-tag-falls-back-to-name ()
+ "With no displayName, the tag slugs the name."
+ (should (string= "@eric_bell" (pearl--assignee-tag '(:name "Eric Bell")))))
+
+(ert-deftest test-pearl-normalize-user-captures-display-name ()
+ "The normalized user carries Linear's displayName so the @-tag can use the
+short handle while the drawer keeps the fuller name."
+ (let ((u (pearl--normalize-user '((id . "u1") (name . "Nerses Ohanyan")
+ (displayName . "nerses") (email . "n@x")))))
+ (should (string= "nerses" (plist-get u :display-name)))
+ (should (string= "Nerses Ohanyan" (plist-get u :name)))))
+
(ert-deftest test-pearl-assignee-tag-nil-when-unassigned ()
"No assignee yields no tag."
(should-not (pearl--assignee-tag nil)))