127 lines
6.2 KiB
Markdown
127 lines
6.2 KiB
Markdown
# Noise Phase 1-3 Implementation Plan
|
|
|
|
**Goal:** Build a minimal Emacs Signal client with JSON-RPC connectivity to signal-cli, SQLite persistence for contacts/conversations/messages, and a chat-list buffer with minibuffer conversation switcher.
|
|
|
|
**Architecture:** Five Elisp files with ERT tests. Each file is independently testable. No external Emacs packages beyond built-ins (url, json, sqlite, ert, cl-lib).
|
|
|
|
**Tech Stack:** Emacs Lisp 27+, signal-cli JSON-RPC over HTTP (localhost:9128)
|
|
|
|
---
|
|
### File Structure
|
|
|
|
```
|
|
noise.el - defgroup, defcustom, noise-mode, noise-check-connection
|
|
noise-rpc.el - JSON-RPC HTTP client: noise-rpc-call, noise-rpc-check
|
|
noise-db.el - SQLite schema (contacts, conversations, messages, members), CRUD
|
|
noise-chat-list.el - *signal* buffer, ibuffer-style rendering
|
|
noise-switcher.el - minibuffer completing-read fuzzy switcher
|
|
test/
|
|
noise-rpc-test.el
|
|
noise-db-test.el
|
|
noise-chat-list-test.el
|
|
noise-switcher-test.el
|
|
```
|
|
|
|
---
|
|
### Task 1: noise.el — Package Skeleton
|
|
|
|
Create `noise.el` with:
|
|
- defgroup noise
|
|
- defcustom noise-signal-cli-address (default "http://localhost:9128")
|
|
- noise-mode minor mode (placeholder, message on enable)
|
|
- Byte-compile check
|
|
- Commit: "feat: add noise.el package skeleton with defcustom and minor mode"
|
|
|
|
---
|
|
### Task 2: noise-rpc.el — JSON-RPC Client
|
|
|
|
Create `test/noise-rpc-test.el` first (5 tests):
|
|
- noise-rpc--build-request-no-params: valid JSON-RPC 2.0 without params
|
|
- noise-rpc--build-request-with-params: params included as alist
|
|
- noise-rpc--parse-response-success: parses result field
|
|
- noise-rpc--parse-response-error: signals noise-rpc-error on error
|
|
- noise-rpc--parse-response-malformed: signals noise-rpc-error on bad JSON
|
|
|
|
Create `noise-rpc.el`:
|
|
- define-error noise-rpc-error
|
|
- noise-rpc--build-request: JSON-RPC 2.0 request with plist-to-alist conversion
|
|
- noise-rpc--plist-to-alist: convert :key val plist to alist
|
|
- noise-rpc-call: POST to {address}/api/v1/rpc, parse response, signal error on connection failure
|
|
- noise-rpc--parse-response: parse JSON, extract result or signal noise-rpc-error
|
|
- noise-rpc-check: call getVersion method
|
|
|
|
Update noise.el: add (require 'noise-rpc), noise-check-connection command
|
|
|
|
Test: 5 tests pass, byte-compiles. Commit.
|
|
|
|
---
|
|
### Task 3: noise-db.el — SQLite Database Layer
|
|
|
|
Create `test/noise-db-test.el` first (9 tests):
|
|
- noise-db-init-creates-file: file exists + connection active
|
|
- noise-db-tables-exist: contacts, conversations, messages, conversation_members tables created
|
|
- noise-db-upsert-and-get-contact: insert/retrieve contact
|
|
- noise-db-upsert-contact-update: upsert updates existing
|
|
- noise-db-get-all-contacts: fetch all contacts
|
|
- noise-db-upsert-and-get-conversation: insert conversation with members
|
|
- noise-db-get-all-conversations-ordered: ordered by last_message_time DESC
|
|
- noise-db-insert-message: insert message, updates conversation preview
|
|
- noise-db-search-contacts: fuzzy search by name
|
|
|
|
Create `noise-db.el`:
|
|
- defcustom noise-db-file (default ~/.emacs.d/noise/noise.db)
|
|
- Schema tables: contacts, conversations, messages, conversation_members (CREATE TABLE IF NOT EXISTS)
|
|
- Indexes: idx_messages_conversation_time, idx_conversations_time
|
|
- noise-db-init, noise-db--open, noise-db--close, noise-db--exec, noise-db--query
|
|
- CRUD: noise-db-upsert-contact, noise-db-get-contact, noise-db-get-all-contacts
|
|
- CRUD: noise-db-upsert-conversation, noise-db-get-conversation, noise-db-get-all-conversations
|
|
- CRUD: noise-db-insert-message, noise-db-get-messages
|
|
- Queries: noise-db-get-conversation-members, noise-db-conversations-for-contact
|
|
- Search: noise-db-search-contacts, noise-db-search-conversations
|
|
|
|
Test: 9 tests pass, byte-compiles. Commit.
|
|
|
|
---
|
|
### Task 4: noise-chat-list.el — Chat List Buffer
|
|
|
|
Create `test/noise-chat-list-test.el` first (5 tests):
|
|
- noise-chat-list--format-time-today: returns HH:MM pattern
|
|
- noise-chat-list--format-time-yesterday: returns Ddd pattern
|
|
- noise-chat-list--format-time-older: returns Mon DD pattern
|
|
- noise-chat-list--format-row-with-unread: includes fringe char and name
|
|
- noise-chat-list--format-row-no-unread: excludes fringe char
|
|
|
|
Create `noise-chat-list.el`:
|
|
- noise-chat-list-mode derived from special-mode, read-only, header-line "U Name Last message When"
|
|
- Keymap: n (next-line), p (previous-line), RET (noise-chat-list-open), m (mark read), g (refresh), / (filter), q (quit-window)
|
|
- noise-chat-list entry point: init db, switch to *signal* buffer, enter mode, render
|
|
- noise-chat-list--render: iterate conversations from db, insert formatted rows, set mode-line with chat/unread counts
|
|
- noise-chat-list--format-row: format with: fringe glyph (● or space), unread count (right-aligned 3), bold name (18 chars truncated), sender-prefixed preview with "me:" for own messages (36 chars truncated), relative timestamp
|
|
- noise-chat-list--format-time: today HH:MM, this week Ddd, older Mon DD using format-time-string
|
|
- noise-chat-list-open: (placeholder) lookup conversation at point, message the name
|
|
- noise-chat-list-mark-read/refresh/filter: placeholder messages
|
|
|
|
Update noise.el: (require 'noise-chat-list), noise-mode enables calls (noise-chat-list)
|
|
|
|
Test: 5 tests pass, byte-compiles. Commit.
|
|
|
|
---
|
|
### Task 5: noise-switcher.el — Minibuffer Switcher
|
|
|
|
Create `test/noise-switcher-test.el` first (5 tests):
|
|
- noise-switcher--collection-includes-conversations: collection returns conversation entries
|
|
- noise-switcher--collection-includes-contacts-without-conversations: contacts with no conversation shown
|
|
- noise-switcher--format-candidate-with-unread: includes unread count annotation
|
|
- noise-switcher--format-candidate-no-unread: annotation omits unread
|
|
- noise-switcher--filter-matches-fuzzy: candidates filtered by substring match
|
|
|
|
Create `noise-switcher.el`:
|
|
- noise-switcher command: completes over conversations + contacts-without-conversations
|
|
- noise-switcher--collection: returns list of (display . id) cons cells for completing-read
|
|
- noise-switcher--format-candidate: "NAME TYPE · UNREAD · TIME" following mockup
|
|
- noise-switcher--filter: substring fuzzy match on input
|
|
- On selection: conversation exists → message name; contact only → create conversation stub, message contact name
|
|
- Annotation property on each candidate for completing-read display
|
|
|
|
Test: 5 tests pass, byte-compiles. Commit.
|