Compare commits

..

3 Commits

Author SHA1 Message Date
jbrechtel 0151d4ad02 UI updates 2026-06-15 11:01:26 -04:00
jbrechtel 6a5d4b3c1d fix: remove leading space from chat list row, aligning with header
The row started with ' ●' (space+fringe) but the header starts with
'  U' (two spaces).  Removing the extra space brings the row columns
in line with the header columns, matching the mockup.
2026-06-14 15:33:02 -04:00
jbrechtel 71691a5261 ui: align chat list and conversation buffer with mockups
Chat list (*signal*):
- Fringe ● now uses warning face (yellow), matching mockup's
  unread indicator color
- Modeline trailing dashes extended for visual balance

Conversation buffer (*signal:NAME*):
- Compose prompt > changed from bold to link face (blue),
  matching mockup's signal-blue prompt
- Attachment rendering: [attachment: ...] → [photo: ...]
- Modeline: (Signal Chat) → (Signal Chat Fill)
- Added header-line with key hints (RET send, M-RET newline,
  C-c C-g refresh), matching mockup's discoverability goal
2026-06-14 13:00:29 -04:00
4 changed files with 46 additions and 20 deletions
+26 -11
View File
@@ -32,9 +32,13 @@
(setq buffer-read-only t) (setq buffer-read-only t)
(setq-local header-line-format (setq-local header-line-format
(concat " U " (concat " U "
(format "%-20s" "Name") (format "%-18s" "Name")
(format "%-40s" "Last message") (format "%-42s" "Last message")
"When"))) "When"))
(when (bound-and-true-p evil-mode)
(let ((m (make-sparse-keymap)))
(define-key m (kbd "RET") #'noise-chat-list-open)
(setq-local evil-normal-state-local-map m))))
;;;###autoload ;;;###autoload
(defun noise-chat-list () (defun noise-chat-list ()
@@ -50,10 +54,21 @@
(let ((inhibit-read-only t) (let ((inhibit-read-only t)
(conversations (noise-db-get-all-conversations))) (conversations (noise-db-get-all-conversations)))
(erase-buffer) (erase-buffer)
;; Clear old fringe overlays
(remove-overlays (point-min) (point-max) 'noise-fringe t)
(if (null conversations) (if (null conversations)
(insert "No conversations. Sync from signal-cli with `g`.\n") (insert "No conversations. Sync from signal-cli with `g`.\n")
(dolist (conv conversations) (dolist (conv conversations)
(insert (noise-chat-list--format-row conv) "\n"))) (let ((unread (or (gethash "unread_count" conv) 0))
(row (noise-chat-list--format-row conv)))
(insert row "\n")
(when (> unread 0)
(let ((ov (make-overlay (line-beginning-position 0)
(line-beginning-position 0))))
(overlay-put ov 'noise-fringe t)
(overlay-put ov 'before-string
(propertize " " 'display
'(left-fringe filled-rectangle warning))))))))
(noise-chat-list--update-modeline conversations) (noise-chat-list--update-modeline conversations)
(goto-char (point-min)) (goto-char (point-min))
(set-buffer-modified-p nil))) (set-buffer-modified-p nil)))
@@ -66,7 +81,7 @@
conversations) conversations)
:initial-value 0))) :initial-value 0)))
(setq mode-line-format (setq mode-line-format
(format " ⌁ %s %d chats %s %s (%s) ─ U:%%- ─ Top ───" (format " ⌁ %s %d chats %s %s (%s) ─ U:%%- ─ Top ────────────"
(propertize "*signal*" 'face 'bold) (propertize "*signal*" 'face 'bold)
total total
(if (> unread-total 0) (if (> unread-total 0)
@@ -93,13 +108,13 @@ Today: HH:MM, this week: Ddd, older: Mon DD."
(defun noise-chat-list--format-row (conv) (defun noise-chat-list--format-row (conv)
"Format a single conversation row for display. "Format a single conversation row for display.
CONV is a hash table with string keys from noise-db." CONV is a hash table with string keys from noise-db.
The unread dot is rendered in the Emacs fringe, not inline."
(let* ((unread (or (gethash "unread_count" conv) 0)) (let* ((unread (or (gethash "unread_count" conv) 0))
(name (or (gethash "name" conv) "Unknown")) (name (or (gethash "name" conv) "Unknown"))
(preview (or (gethash "last_message_preview" conv) "")) (preview (or (gethash "last_message_preview" conv) ""))
(sender (or (gethash "last_message_sender" conv) "")) (sender (or (gethash "last_message_sender" conv) ""))
(timestamp (or (gethash "last_message_time" conv) 0)) (timestamp (or (gethash "last_message_time" conv) 0))
(fringe (if (> unread 0) "" " "))
(unread-str (if (> unread 0) (format "%3d" unread) " ")) (unread-str (if (> unread 0) (format "%3d" unread) " "))
(name-str (truncate-string-to-width name 18 nil nil "")) (name-str (truncate-string-to-width name 18 nil nil ""))
(preview-prefix (preview-prefix
@@ -107,11 +122,11 @@ CONV is a hash table with string keys from noise-db."
((string= sender "") preview) ((string= sender "") preview)
((string= sender "me") (concat "me: " preview)) ((string= sender "me") (concat "me: " preview))
(t (concat sender ": " preview)))) (t (concat sender ": " preview))))
(preview-str (truncate-string-to-width preview-prefix 36 nil nil "")) (preview-str (truncate-string-to-width preview-prefix 42 nil nil ""))
(time-str (noise-chat-list--format-time timestamp))) (time-str (noise-chat-list--format-time timestamp)))
(concat " " fringe " " unread-str " " (concat unread-str " "
(propertize (format "%-18s" name-str) 'face 'bold) " " (propertize (format "%-18s" name-str) 'face 'bold)
preview-str " " (propertize (format "%-42s" preview-str) 'face 'shadow)
(propertize time-str 'face 'shadow)))) (propertize time-str 'face 'shadow))))
(defun noise-chat-list--conversation-at-point () (defun noise-chat-list--conversation-at-point ()
+16 -5
View File
@@ -37,10 +37,21 @@
map) map)
"Keymap for `noise-conversation-mode'.") "Keymap for `noise-conversation-mode'.")
(defconst noise-conversation--mode-header
(concat (propertize "RET" 'face 'help-key-binding) " send "
(propertize "M-RET" 'face 'help-key-binding) " newline "
(propertize "C-c C-g" 'face 'help-key-binding) " refresh")
"Header line showing key hints for `noise-conversation-mode'.")
(define-derived-mode noise-conversation-mode text-mode "Signal Chat" (define-derived-mode noise-conversation-mode text-mode "Signal Chat"
"Major mode for a Noise Signal conversation buffer. "Major mode for a Noise Signal conversation buffer.
\\{noise-conversation-mode-map}" \\{noise-conversation-mode-map}"
:group 'noise) :group 'noise
(setq-local header-line-format noise-conversation--mode-header)
(when (bound-and-true-p evil-mode)
(let ((m (make-sparse-keymap)))
(define-key m (kbd "RET") #'noise-conversation-send)
(setq-local evil-normal-state-local-map m))))
;;;###autoload ;;;###autoload
(defun noise-conversation-open (conversation-id) (defun noise-conversation-open (conversation-id)
@@ -107,7 +118,7 @@ Preserves any input already typed at the prompt."
(propertize (make-string noise-conversation--width ?─) (propertize (make-string noise-conversation--width ?─)
'face 'shadow) 'face 'shadow)
"\n" "\n"
(propertize "> " 'face 'bold)) (propertize "> " 'face 'link))
(add-text-properties (point-min) (point) (add-text-properties (point-min) (point)
'(read-only t '(read-only t
front-sticky (read-only) front-sticky (read-only)
@@ -149,7 +160,7 @@ Preserves any input already typed at the prompt."
(or (gethash "attachment_path" msg) "")))) (or (gethash "attachment_path" msg) ""))))
(setq body (concat body (if (string-empty-p body) "" " ") (setq body (concat body (if (string-empty-p body) "" " ")
(propertize (propertize
(format "[attachment: %s — RET to view]" file) (format "[photo: %s — RET to view]" file)
'face 'link))))) 'face 'link)))))
(let ((line (concat (propertize time-str 'face 'shadow) " " (let ((line (concat (propertize time-str 'face 'shadow) " "
(propertize name 'face 'bold) (propertize name 'face 'bold)
@@ -167,13 +178,13 @@ Preserves any input already typed at the prompt."
(defun noise-conversation--update-modeline () (defun noise-conversation--update-modeline ()
"Update the mode line with the E2E indicator and typing status." "Update the mode line with the E2E indicator and typing status."
(setq mode-line-format (setq mode-line-format
(format " %s %s %s%s ─ U:%%- ─ Bot ───" (format " %s %s %s%s ─ U:%%- ─ Bot ───────────"
(propertize "⌁ E2E" 'face 'success) (propertize "⌁ E2E" 'face 'success)
(propertize (buffer-name) 'face 'bold) (propertize (buffer-name) 'face 'bold)
(if noise-conversation--typing (if noise-conversation--typing
(format "%s is typing… " noise-conversation--typing) (format "%s is typing… " noise-conversation--typing)
"") "")
"(Signal Chat)")) "(Signal Chat Fill)"))
(force-mode-line-update)) (force-mode-line-update))
;;; Composing and sending ;;; Composing and sending
+2 -2
View File
@@ -39,7 +39,7 @@
(noise-chat-list-test--make-row "conv-1" "Sarah" 3 (noise-chat-list-test--make-row "conv-1" "Sarah" 3
"are you taking the girls?" "are you taking the girls?"
"Sarah" (float-time))))) "Sarah" (float-time)))))
(should (string-match "" row)) (should (string-match " 3" row))
(should (string-match "Sarah" row)) (should (string-match "Sarah" row))
(should (string-match "are you taking" row)))) (should (string-match "are you taking" row))))
@@ -49,4 +49,4 @@
(noise-chat-list-test--make-row "conv-2" "Mark Okafor" 0 (noise-chat-list-test--make-row "conv-2" "Mark Okafor" 0
"merged" "me" (float-time))))) "merged" "me" (float-time)))))
(should (string-match "Mark Okafor" row)) (should (string-match "Mark Okafor" row))
(should-not (string-match "" row)))) (should (string-match "^ " row))))
+2 -2
View File
@@ -132,7 +132,7 @@
(should (string= "draft text" (noise-conversation--current-input)))))) (should (string= "draft text" (noise-conversation--current-input))))))
(ert-deftest noise-conversation-format-attachment () (ert-deftest noise-conversation-format-attachment ()
"Attachment messages render the [attachment: ...] hint." "Attachment messages render the [photo: ...] hint."
(noise-conversation-test--with-db (noise-conversation-test--with-db
(noise-conversation-test--seed) (noise-conversation-test--seed)
(noise-db-insert-message "+1" :timestamp 1717930300.0 (noise-db-insert-message "+1" :timestamp 1717930300.0
@@ -140,7 +140,7 @@
:has-attachment t :has-attachment t
:attachment-path "IMG_2041.jpg") :attachment-path "IMG_2041.jpg")
(with-current-buffer (noise-conversation-open "+1") (with-current-buffer (noise-conversation-open "+1")
(should (string-match-p "\\[attachment: IMG_2041\\.jpg — RET to view\\]" (should (string-match-p "\\[photo: IMG_2041\\.jpg — RET to view\\]"
(buffer-string)))))) (buffer-string))))))
(ert-deftest noise-conversation-typing-indicator () (ert-deftest noise-conversation-typing-indicator ()