siggy 1.4.2

Terminal-based Signal messenger client with vim keybindings
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# Changelog

## v1.4.2

### Bug fixes

- **Paste attachment deleted before send** - `/paste` images were deleted
  immediately after dispatching the RPC, before signal-cli could read the file.
  Now defers cleanup until signal-cli confirms or fails the send (#229)

### Internal

- **Unified event loop** - consolidated `run_app` and `run_demo_app` into a
  single function using a `MessagingBackend` enum. Demo mode now inherits all
  main loop features (#214)
- **File picker extraction** - moved file picker state and logic out of the App
  god object into `domain::FilePickerState`, the first step toward #221 (#226)
- **Dead code cleanup** - marked `find_settings_profile` as test-only with
  `#[cfg(test)]` (#225)

### Infrastructure

- **Homebrew tap** - `brew tap johnsideserf/siggy && brew install siggy` now
  works on macOS. Formula auto-updates on each release (#227)
- **Mermaid diagrams** - added interactive architecture, data flow, and module
  dependency diagrams to the developer guide

Thanks to [@Dowsley](https://github.com/Dowsley) for #214 and #226,
[@shwoop](https://github.com/shwoop) for #227 and #229,
and [@zamadye](https://github.com/zamadye) for #225.

---

## v1.4.1

### New features

- **Clipboard paste** -- `/paste` (alias `/pa`) pastes clipboard contents directly
  into a conversation. Images (e.g. screenshots) are saved as a temp PNG and staged
  as an attachment with inline preview. Text pastes into the input buffer (#217)

### Bug fixes

- **Typing indicator bleed** -- group typing events were keyed by sender phone
  number instead of group ID, causing indicators to appear in unrelated
  conversations. Now correctly scoped to the conversation they belong to (#215)

### Enhancements

- **Outgoing image preview** -- sending an image attachment now renders an inline
  preview in the chat area immediately, matching the behavior of received images
  (#217)

Thanks to [@shwoop](https://github.com/shwoop) for both changes in this release.

---

## v1.4.0

### New features

- **Settings profiles** -- predefined profiles (Default, Compact, Minimal) that
  apply a set of display settings at once. Select from `/settings` > Profile (#210)
- **Date separators** -- day-boundary separator lines between messages showing
  "Today", "Yesterday", or full date. Toggle via `/settings` (#210)
- **Background image** -- set a background image for the chat area via
  `background_image` in config.toml. Renders as dimmed halfblock art behind
  messages (#210)
- **Startup spinner** -- shows a spinner with status messages (syncing, loading
  contacts/groups/identities) during startup instead of a blank screen (#196)
- **Quit confirmation** -- prompts before quitting if there's unsent text in the
  input buffer (#203)

### Bug fixes

- **Disappearing message icon clipping** -- the timer icon was overlapping the
  timestamp on narrow windows. Now properly spaced (#201)
- **iTerm2 image squishing** -- images squished vertically when the chat was
  partially scrolled. Fixed pixel-level offset calculation (#198)

### Enhancements

- **Kitty Unicode Placeholders** -- native image rendering now uses Kitty's
  Unicode placeholder method for flicker-free, properly composited images (#195)
- **Settings hints** -- settings menu items now show a brief description of what
  each toggle does (#193)
- **Sidebar reorder on activity** -- the sidebar now reorders conversations to
  the top when messages are sent or received, keeping active chats visible (#205,
  #212)

Thanks to [@Dowsley](https://github.com/Dowsley) for #195, #198, and #205.

---

## v1.3.3

### Bug fixes

- **Kitty image ghosting** -- images that scrolled out of view persisted on
  screen. Now deletes all Kitty placements before each redraw (#180)
- **Kitty image stretching** -- partially visible images were scaled into fewer
  rows instead of being cropped. Now uses Kitty source-rect params to crop at
  the pixel level (#180)
- **Kitty image flickering** -- images were deleted and re-transmitted every
  frame even when unchanged. Now tracks the previous frame's visible images
  and skips redundant redraws (#180)
- **Native image cache consistency** -- the first visible height would win in
  the cache, producing wrong results at different sizes. Images are now always
  encoded at full dimensions and cropped at display time (#180)

### Enhancements

- **Synchronized updates** -- the entire render cycle (clear + text draw +
  image overlay) is now wrapped in a synchronized update, eliminating flash
  on conversation switch and during scroll (#180)
- **Suppressed Kitty responses** -- added `q=2` to Kitty graphics commands to
  prevent response bytes from leaking into stdin (#180)

Thanks to [@Dowsley](https://github.com/Dowsley) for this release.

---

## v1.3.2

### Bug fixes

- **Cursor blink fix** -- the event loop was redrawing every 50ms regardless of
  state changes, resetting the terminal's cursor blink timer. Now frames are only
  drawn when state actually changes (#177)

### Enhancements

- **Proxy config** -- added a `proxy` field to `config.toml` for Signal TLS
  proxy URLs, passed through to signal-cli as `--proxy`. Useful for connecting
  in censored regions (#178)

### Developer

- **Fuzz testing** -- added `cargo-fuzz` harnesses for JSON-RPC parsing, UTF-8
  cursor operations, keybinding parsing, and slash command parsing (#179)

---

## v1.3.1

### Bug fixes

- **Image attachments** -- fixed image attachments not rendering after restart on
  Unix (#175)
- **Quit aliases** -- added `:q` and `:quit` as aliases for `/quit` (#174)

---

## v1.3.0

### Bug fixes

- **Unicode input crash** -- typing accented characters (á, ã, ó, é) caused a
  panic due to cursor arithmetic assuming single-byte characters. All cursor
  movement, insertion, and deletion now correctly handle multi-byte UTF-8
  characters (#166)

### Security

- **SQLite secure_delete** -- added `PRAGMA secure_delete = ON` so deleted
  message content is zeroed in database free pages rather than left recoverable
  (#161)
- **Debug log PII redaction** -- `--debug` now masks phone numbers and message
  bodies in log output. Use `--debug-full` for unredacted output when needed
  (#163)
- **Security documentation** -- added a comprehensive security page to the
  docsite covering trust model, credential storage, encryption at rest, and
  privacy features (#164)

### Enhancements

- **Notification preview in settings** -- the `notification_preview` setting
  ("full", "sender", "minimal") is now accessible as a cycle toggle in the
  `/settings` overlay (#162)

---

## v1.2.2

### Bug fixes

- **Uppercase keybindings broken** -- Shift+J/K (focus next/prev message),
  G (scroll to bottom), N (prev search), Y (copy all), and other uppercase
  bindings stopped working after the keybindings refactor. crossterm sends
  uppercase chars with a SHIFT modifier that the lookup didn't account for
- **j/k scroll snapping back** -- pressing j/k to scroll would briefly shift
  the viewport then immediately snap back to the bottom. The draw code was
  deriving a focus index that triggered a "keep focused message visible"
  adjustment on the next frame

---

## v1.2.1

### Bug fixes

- **Autocomplete popup crash** -- fixed a panic when opening the autocomplete
  popup (`/` commands, `@` mentions) in a small terminal window. The popup
  now clamps to available space and skips rendering if the terminal is too small

---

## v1.2.0

### New features

- **Configurable keybindings** -- keybindings are now fully configurable via
  three built-in profiles (Default, Emacs, Minimal), custom TOML profiles in
  `~/.config/siggy/keybindings/`, and per-key overrides in
  `~/.config/siggy/keybindings.toml`. Switch profiles and rebind keys live in
  the new `/keybindings` overlay. The help overlay dynamically reflects active
  bindings (#138)
- **Message history pagination** -- scrolling to the top of a conversation
  automatically loads older messages from the database, with a loading indicator
  while fetching (#158)
- **Multi-line message input** -- press Alt+Enter (or Shift+Enter) to insert
  newlines in your message before sending (#157)

---

## v1.1.0

### New features

- **Forward messages** -- press `f` in Normal mode to forward a message to
  another conversation via a filterable picker overlay (#139)
- **Scroll position memory** -- switching between conversations now remembers
  and restores your scroll position (#137)
- **Notification preview levels** -- new `notification_preview` config option
  with three levels: `"full"` (default), `"sender"`, or `"minimal"` (#132)
- **Clipboard auto-clear** -- clipboard is automatically cleared 30 seconds
  after copying a message. Configurable via `clipboard_clear_seconds` (#131)
- **Tree-style connectors** -- quotes and link previews now use curved Unicode
  box-drawing characters (``, ``, ``) for visual clarity (#141)

### Security

- **Identity verification fix** -- `/verify` now uses `verifiedSafetyNumber`
  instead of `trustAllKnownKeys`, requiring explicit safety number confirmation
  before trusting a contact's identity key (#144)
- **Secure debug log** -- debug log moved from CWD to `~/.cache/siggy/debug.log`
  with 10MB rotation and a startup warning (#134)
- **Incognito attachment isolation** -- `--incognito` mode now redirects
  attachment downloads to a temp directory that is auto-deleted on exit (#133)

---

## v1.0.1

### Security

- **OSC 8 escape injection fix** -- URLs in messages are now sanitized before
  being embedded in terminal hyperlink escape sequences, preventing crafted
  URLs from manipulating terminal state (title, colors, screen)
- **Attachment path traversal fix** -- attachment filenames from signal-cli are
  now sanitized by replacing path separators and `..` traversal sequences,
  preventing writes outside the configured download directory

---

## v1.0.0

### Rename to siggy

- **Renamed from signal-tui to siggy** -- the binary, package, config paths,
  data paths, and database filename are all now "siggy" (#127)
- **Automatic migration** -- existing config directories, data directories,
  and database files are seamlessly migrated from the old "signal-tui" paths
  on first launch. No manual action required
- **Published to crates.io** -- install with `cargo install siggy` (closes #11)

### Docsite

- **Brand theme** -- docsite color palette updated from gray mIRC to siggy's
  navy-blue brand colors in both light and dark modes (#128)
- **Logo integration** -- siggy logo and favicon added to the docsite intro
  page and menu bar

### Repo hygiene

- **Cargo.lock tracked** -- binary crate now correctly tracks its lockfile
- **.gitignore cleanup** -- added IDE directories and platform artifacts

---

## v0.9.0

### Pinned messages

- **Pin and unpin messages** -- press `p` in Normal mode or use the action menu
  to pin a message. Choose a pin duration (forever, 24h, 7d, 30d). Pinned
  messages show a banner at the top of the chat area. Unpin by pressing `p`
  again. Pin state syncs across devices (closes #65)

### Link previews

- **URL preview display** -- messages containing URLs now show link preview
  cards with title, description, and thumbnail image (when available). Toggle
  via `/settings` > "Link previews" (closes #63)

### Polls

- **Create polls** -- use `/poll "question" "opt1" "opt2"` to create a poll.
  Add `--single` to restrict to single-select. Polls display as inline bar
  charts showing vote counts and percentages
- **Vote in polls** -- press Enter on a poll message to open the vote overlay.
  Select options with Space, confirm with Enter. Multi-select polls allow
  toggling multiple options (closes #64)

### Identity verification

- **`/verify` command** -- verify the identity keys of your contacts. In 1:1
  chats, shows the safety number and trust level. In groups, browse members
  and verify individually. Trust/untrust identity keys directly from the
  overlay (closes #70)

### Profile editor

- **`/profile` command** -- edit your Signal profile directly from the TUI.
  Change your given name, family name, about text, and about emoji. Navigate
  with j/k, Enter to edit fields inline, and Save to push changes via
  `updateProfile` RPC (closes #69)

### About overlay

- **`/about` command** -- shows app version, description, author, license,
  and repository link. Press any key to close

### Sidebar position

- **Left/right sidebar** -- new setting to place the sidebar on the right
  side instead of the default left. Toggle via `/settings` > "Sidebar on
  right" (closes #125)

### Bug fixes

- **Mouse selection** -- fixed mouse click positioning in the input bar,
  right-click paste, and slow Ctrl+V behavior (#124)
- **Poll vote counting** -- votes now correctly use `vote_count` as a
  multiplier instead of always counting as 1 (#122)
- **Mention parsing** -- fixed mention field names to match signal-cli's
  actual protocol (#108)

### Internal

- **Test coverage** -- added unit tests for UI helpers and event handlers,
  migrated to rstest parameterized tests (#109, #113, #120)
- **Robustness** -- removed unsafe unwraps, surfaced DB errors in status bar,
  used binary search for message insertion (#118, #119)

---

## v0.8.0

### Disappearing messages

- **Timer support** -- siggy now honors disappearing message timers.
  Messages auto-expire after the configured duration, with a countdown shown
  in the chat area. Set the timer with `/disappearing <duration>` (alias `/dm`)
  using values like `30s`, `5m`, `1h`, `1d`, `1w`, or `off` (closes #61)

### Group management

- **`/group` command** -- manage groups directly from the TUI (alias `/g`).
  Opens a menu with options to view members, add/remove members, rename the
  group, create a new group, or leave a group. Add/remove members use a
  type-to-filter contact picker (closes #26)

### Message requests

- **Unknown sender detection** -- messages from unknown senders (not in your
  contacts) are now flagged as message requests. A banner appears with options
  to accept (start chatting) or delete the conversation. Unaccepted
  conversations do not trigger notifications or send read receipts (closes #62)

### Block and unblock

- **`/block` and `/unblock` commands** -- block or unblock the current
  conversation's contact or group. Blocked conversations do not trigger
  notifications, read receipts, or typing indicators (closes #60)

### Mouse support

- **Clickable sidebar** -- click conversations in the sidebar to switch
- **Scrollable messages** -- scroll wheel in the chat area
- **Overlay navigation** -- scroll wheel navigates lists in overlays
- **Click to position cursor** -- click in the input bar to place the cursor
- Configurable via `/settings` > "Mouse support" (default: on) (closes #17)

### Color themes

- **Selectable themes** -- open the theme picker with `/theme` (alias `/t`)
  or from `/settings` > Theme. Includes built-in themes with customizable
  sidebar, chat, status bar, and accent colors (closes #18)

### Desktop notifications

- **OS-level notifications** -- cross-platform desktop notifications using
  `notify-rust` (Linux D-Bus, macOS NSNotification, Windows WinRT toast).
  Shows sender name and message preview. Toggle via `/settings` > "Desktop
  notifications" (default: off) (closes #19)

### Bug fixes

- **Mouse capture on Windows** -- mouse support no longer breaks after
  signal-cli starts on Windows. Spawning `signal-cli.bat` (cmd.exe) was
  resetting console input mode flags (#105)

### Database

- **Migration v7** -- adds `expiration_timer` to `conversations` and
  `expires_in_seconds`, `expiration_start_ms` to `messages` (disappearing
  messages)
- **Migration v8** -- adds `accepted` column to `conversations` (message
  requests)
- **Migration v9** -- adds `blocked` column to `conversations` (block/unblock)

---

## v0.7.0

### Text styling

- **Rich text rendering** -- messages with Signal formatting now display with
  proper styling: **bold**, *italic*, ~~strikethrough~~, `monospace`, and spoiler
  text. Spoiler content is hidden behind block characters (closes #66)

### Sticker messages

- **Sticker display** -- incoming stickers are now shown as `[Sticker: emoji]`
  in the chat area instead of being silently dropped (closes #67)

### View-once messages

- **View-once handling** -- view-once messages display as `[View-once message]`
  with attachments suppressed, respecting the ephemeral intent (closes #68)

### Cross-device read sync

- **Read state sync** -- when you read messages on your phone or another linked
  device, siggy marks those conversations as read and updates unread counts
  automatically (closes #71)

### System messages

- **Missed calls** -- missed voice and video calls now show as system messages
- **Safety number changes** -- a warning appears when a contact's safety number
  changes
- **Group updates** -- group metadata changes (member adds/removes) display as
  system messages
- **Disappearing message timer** -- changes to the expiration timer show a
  human-readable message (e.g. "Disappearing messages set to 1 day")

### Message action menu

- **Enter key menu** -- press Enter in Normal mode on a focused message to open
  a contextual action menu. Available actions (shown with key hints): Reply (q),
  Edit (e), React (r), Copy (y), Delete (d). Navigate with j/k, press Enter to
  execute, or use the shortcut key directly (closes #85)

### Bug fixes

- **"New messages" bar** -- the unread separator no longer persists after viewing
  a conversation with new messages (#90)

---

## v0.6.1

### Bug fixes

- **j/k scroll fixed** -- viewport no longer gets stuck when scrolling with
  `j`/`k`. The root cause was the message window expanding in lockstep with
  scroll offset, keeping the viewport position constant (#84)
- **J/K navigation in short conversations** -- `J`/`K` message jumping now
  works even when all messages fit the viewport (no scroll offset needed) (#84)
- **Edit preserves quotes** -- editing a quoted message no longer strips the
  original quote on remote clients. The wire-format phone number is now
  preserved through display name resolution (#84)
- **Contact names no longer revert to phone numbers** -- conversations would
  permanently show phone numbers in the sidebar when messages arrived before
  the contact list synced. Fixed by preventing phone-number fallback names
  from overwriting real display names in the database (#84)
- **Contact name recovery on startup** -- 1:1 conversations still named as
  phone numbers (e.g. when signal-cli's contact list has no cached profile
  name) now recover the correct name from stored message sender fields (#86)
- **Reaction sender names after reload** -- reaction senders no longer revert
  to phone numbers after restarting the app (#80)
- **Non-contact name resolution** -- display names for non-contacts in
  reactions and quotes are now resolved correctly (#83)
- **Mention placeholders in quotes** -- U+FFFC placeholder characters from
  @mentions are now stripped from quoted text (#79)

### Improvements

- **Loading screen** -- a loading indicator now appears during startup while
  contacts and groups sync from signal-cli (#81, #82)
- **Install scripts updated** -- Windows and macOS install scripts now
  reference Java 25+ (required by signal-cli 0.14). The Windows script
  checks the actual Java version before installing signal-cli (#87)

---

## v0.6.0

### Reply, edit, and delete messages

- **Quote reply** -- press `q` in Normal mode on any message to reply with a
  quote. A reply indicator appears above the input box, and the sent message
  includes a quoted block showing the original author and text (closes #15)
- **Edit messages** -- press `e` on your own outgoing message to edit it.
  The original text is loaded into the input buffer for modification. Edited
  messages display an "(edited)" label. Edits sync across devices (closes #24)
- **Delete messages** -- press `d` on any message to open a delete confirmation.
  Outgoing messages offer "delete for everyone" (remote delete) or "delete
  locally". Incoming messages can be deleted locally. Deleted messages show as
  "[deleted]" (closes #23)

### Message search

- **`/search` command** -- search across all conversations with `/search <query>`
  (alias: `/s`). Results appear in a scrollable overlay showing sender, message
  snippet, and conversation name. Press Enter to jump directly to the message in
  context. Use `n`/`N` in Normal mode to cycle through matches (closes #14)
- **Highlight matches** -- search terms are highlighted in the result snippets

### File attachments

- **`/attach` command** -- send files with `/attach` to open a file browser
  overlay. Navigate with `j`/`k`, Enter to select, Backspace to go up a
  directory. The selected file attaches to your next message, shown as a
  pending indicator in the input area (closes #54)

### /join autocomplete

- **Contact and group autocomplete** -- `/join` now offers Tab-completable
  suggestions from your contacts and groups. Type `/join ` and see matching
  names, or keep typing to filter. Groups and contacts are distinguished by
  color (closes #21)

### Send typing indicators

- **Outbound typing** -- siggy now sends typing indicators to your
  conversation partner while you type. Typing state starts on the first
  keypress, auto-stops after 5 seconds of inactivity, and stops immediately
  when you send or switch conversations (closes #58)

### Send read receipts

- **Read receipt sending** -- when you view a conversation, read receipts are
  automatically sent to message senders, letting them know you've read their
  messages. Controlled by the "Send read receipts" toggle in `/settings`
  (closes #59)

### Welcome screen

- **Getting started hints** -- the welcome screen now shows useful commands
  and navigation tips including Tab/Shift+Tab for cycling conversations

### Bug fixes

- **Out-of-order messages** -- messages with delayed delivery timestamps are
  now inserted in correct chronological order (#56)
- **Link highlight** -- fixed background color bleeding on highlighted links
  and J/K message navigation edge cases (#55)

### Database

- **Migration v5** -- adds index on `messages(conversation_id, timestamp_ms)`
  for faster search queries
- **Migration v6** -- adds `is_edited`, `is_deleted`, `quote_author`,
  `quote_body`, `quote_ts_ms`, and `sender_id` columns to the messages table

---

## v0.5.0

### Message reactions

- **Emoji reactions** -- react to any message with `r` in Normal mode to
  open the reaction picker. Navigate with `h`/`l` or `1`-`8`, press
  Enter to send. Reactions display below messages as compact emoji
  badges (e.g. `👍 2 ❤️ 1`) with an optional verbose mode showing
  sender names (closes #16)
- **Reaction sync** -- incoming reactions, sync reactions from other
  devices, and reaction removals are all handled in real time
- **Persistence** -- reactions are stored in the database (migration v4)
  and restored on startup

### @mentions

- **Mention autocomplete** -- type `@` in group chats to open a member
  autocomplete popup. Filter by name, press Tab to insert the mention.
  Works in 1:1 chats too (with the conversation partner)
- **Mention display** -- incoming mentions are highlighted in cyan+bold
  in the chat area

### Visible message selection

- **Focus highlight** -- when scrolling in Normal mode, the focused
  message gets a subtle dark background highlight so you can see exactly
  which message reactions and copy will target
- **`J`/`K` navigation** -- Shift+j and Shift+k jump between actual
  messages, skipping date separators and system messages

### Startup error handling

- **stderr capture** -- signal-cli startup errors (missing Java, bad
  config, etc.) are now captured and displayed in a TUI error screen
  instead of silently failing

### Internal

- Major refactoring across four PRs (#45-#48): extracted shared key
  handlers, data-driven settings system, split `parse_receive_event`
  into sub-functions, modernized test helpers, added persistent debug
  log and pending_requests TTL

---

## v0.4.0

### Contact list

- **`/contacts` command** -- new overlay for browsing all synced contacts,
  with j/k navigation, type-to-filter by name or number, and Enter to
  open a conversation (alias: `/c`) (closes #22)

### Clipboard

- **Copy to clipboard** -- in Normal mode, `y` copies the selected
  message body and `Y` copies the full formatted line
  (`[HH:MM] <sender> body`) to the system clipboard (closes #28)

### Navigation

- **Full timestamp on scroll** -- when scrolling through messages in
  Normal mode, the status bar now shows the full date and time of the
  focused message (e.g. "Sun Mar 01, 2026 12:34:56 PM") (closes #27)

---

## v0.3.3

### Bug fixes

- **Settings persistence** -- changes made in `/settings` are now saved
  to the config file and persist between sessions (fixes #40)
- **Input box scrolling** -- long messages no longer disappear when
  typing past the edge of the input box; text now scrolls horizontally
  to keep the cursor visible (fixes #39)
- **Image preview refresh** -- toggling "Inline image previews" in
  `/settings` now immediately re-renders or clears previews on existing
  messages (fixes #41)

### Settings

- **Tab to toggle** -- Tab key now toggles settings items in the
  `/settings` overlay, alongside Space and Enter

---

## v0.3.2

### Read receipts and delivery status

- **Message status indicators** -- outgoing messages now show delivery
  lifecycle symbols: `` Sending → `` Sent → `` Delivered → `` Read
  `` Viewed
- **Real-time updates** -- status symbols update live as recipients
  receive and read your messages
- **Group receipt support** -- delivery and read receipts work correctly
  in group conversations
- **Race condition handling** -- receipts that arrive before the server
  confirms the send are buffered and replayed automatically
- **Persistent status** -- message status is stored in the database and
  restored on reload (stale "Sending" messages are promoted to "Sent")
- **Nerd Font icons** -- optional Nerd Font glyphs available via
  `/settings` > "Nerd Font icons"
- **Configurable** -- three new settings toggles: "Show read receipts" (on/off),
  "Receipt colors" (colored/monochrome), "Nerd Font icons" (unicode/nerd)

### Debug logging

- **`--debug` flag** -- opt-in protocol logging to `siggy-debug.log`
  for diagnosing signal-cli communication issues

### Database

- **Migration v3** -- adds `status` and `timestamp_ms` columns to the
  messages table (automatic on first run)

---

## v0.3.1

### Image attachments

- **Embedded file links** -- attachment URIs are now hidden behind clickable
  bracket text (e.g. `[image: photo.jpg]`) instead of showing the raw
  `file:///` path
- **Double extension fix** -- filenames like `photo.jpg.jpg` are stripped to
  `photo.jpg` when signal-cli duplicates the extension
- **Improved halfblock previews** -- increased height cap from 20 to 30
  cell-rows for better inline image quality
- **Native image protocols** -- experimental support for Kitty and iTerm2
  inline image rendering, off by default. Enable via `/settings` >
  "Native images (experimental)"
- **Pre-resized encoding** -- native protocol images are resized and cached
  as PNG before sending to the terminal, avoiding multi-megabyte raw file
  transfers every frame

### Attachment lookup

- **MSYS/WSL path fix** -- `find_signal_cli_attachment` now checks both
  platform-native data dirs (`AppData/Roaming`) and POSIX-style
  (`~/.local/share`) where signal-cli stores files under MSYS or WSL.
  Fixes outgoing images sent from Signal desktop not displaying in the TUI.

### Platform

- **Windows Ctrl+C fix** -- suppress the `STATUS_CONTROL_C_EXIT` error on
  exit by disabling the default Windows console handler (crossterm already
  captures Ctrl+C as a key event in raw mode)

### Documentation

- mdBook documentation site with custom mIRC/Win95 light theme and dark mode
  toggle

---

## v0.3.0

Initial public release.

- Terminal Signal client wrapping signal-cli via JSON-RPC
- Vim-style modal input (Normal/Insert modes)
- Sidebar with conversation list, unread counts, typing indicators
- Inline halfblock image previews
- OSC 8 clickable hyperlinks
- SQLite persistence with WAL mode
- Incognito mode (`--incognito`)
- Demo mode (`--demo`)
- First-run setup wizard with QR device linking
- Slash commands: `/join`, `/part`, `/quit`, `/sidebar`, `/help`, `/settings`,
  `/mute`, `/notify`, `/bell`
- Input history (Up/Down recall)
- Autocomplete popup for commands and @mentions
- Configurable notifications (direct/group) with terminal bell
- Cross-platform: Linux, macOS, Windows