Wave Client for Emacs - Project Documentation

Table of Contents

1. Overview

This document tracks the progress, approach, methodology, issues, concerns, and timing for the Wave Client for Emacs revival project.

1.1. Project Goal

Revive and modernize the original 2009-era Google Wave Protocol Emacs client to work with modern infrastructure:

  • Python 3.11+ (was Python 2.x era)
  • Emacs 30.1 (was Emacs 24.x)
  • FreeBSD 14.3 development environment
  • FastAPI/WebSocket backend simulator

2. Methodology

2.1. Multi-Agent Workflow

Using Gas Town (gt) and Beads (bd) for coordinated AI-assisted development:

  • Beads: AI-native issue tracking stored in .beads/ directory
    • Epics for major initiatives
    • Tasks for discrete work items
    • Dependencies between issues
    • Priority levels (P0-P4)
  • Gas Town: Multi-agent workspace manager
    • Rigs for agent configuration
    • Polecats for ephemeral workers
    • Sling for work distribution
    • Stash for handoffs

2.2. Development Approach

  1. Understand First: Read original elisp before modifications
  2. Simulator-First: Build Python server adhering to Wave Protocol
  3. Contract Testing: Verify protocol compliance between elisp and server
  4. Incremental Progress: Small, testable changes with git commits

3. Technical Stack

Component Version Purpose
Python 3.11.13 Server backend
Emacs 30.1 Client runtime
FreeBSD 14.3 Development environment
FastAPI latest REST/WebSocket server
SQLite 3.x Wave data persistence
uv 0.6.4 Python package management
pytest latest Test framework
websockets 15.0.1 WebSocket client for tests
httpx latest HTTP client for tests

4. Architecture

┌─────────────────────┐         ┌─────────────────────┐
│   Emacs Client      │         │   Wave Server       │
│                     │         │   (Python/FastAPI)  │
│  wave-mode.el       │◄────────│                     │
│  wave-list.el       │   WS    │  /ws endpoint       │
│  wave-edit.el       │   +     │  /api/inbox         │
│  wave-client.el     │   HTTP  │  /api/waves/{id}    │
└─────────────────────┘         │  /health            │
                                └─────────────────────┘
                                         │
                                         ▼
                                ┌─────────────────────┐
                                │   SQLite Database   │
                                │   wave_server.db    │
                                └─────────────────────┘

diagram-architecture.png

Figure 1: Wave Client for Emacs — component architecture showing elisp modules, protocol layer, FastAPI server, and test suite

5. Testing

5.1. Test Results Summary (2026-01-10)

Test Suite Tests Passed Status
REST API Extended 18 18 ✓ Pass
WebSocket Extended 14 14 ✓ Pass
Total 32 32 ✓ Pass

5.2. Running Tests

5.2.1. Python Integration Tests

# Start the server first
gmake server

# In another terminal, run tests
gmake test-python

# Or run specific test files
uv run pytest tests/integration/test_rest_extended.py -v
uv run pytest tests/integration/test_ws_extended.py -v

5.2.2. Quick API Smoke Test

gmake test-api

Output:

Testing Wave Server API...
=== Health Check ===
{"status":"healthy","timestamp":"2026-01-10T21:12:13.817809"}

=== Inbox ===
[{"id":"indexwave!indexwave","digest":"Wave Server Index","unread":0,"creator":"system@localhost"},...]

=== Root ===
{"message":"Wave Server API","status":"running","version":"0.2.0"}

5.3. Emacs Batch Mode Testing

Emacs batch mode allows headless testing without a GUI. This is essential for CI/CD pipelines.

5.3.1. Available Make Targets

gmake elisp-version      # Show Emacs version
gmake elisp-load-test    # Test elisp modules load without errors
gmake elisp-check-syntax # Byte-compile check for warnings
gmake elisp-http-inbox   # Fetch inbox via HTTP (requires server)

5.3.2. Emacs Version Check

gmake elisp-version

Output:

Emacs version: GNU Emacs 30.1 (build 1, aarch64-unknown-freebsd14.3)

5.3.3. Load Test (No Server Required)

Tests that all elisp modules load without errors:

gmake elisp-load-test

Output:

Testing elisp module loading...
Loading wave-client.el... done
Loading wave-mode.el... done
Loading wave-list.el... done
Loading wave-edit.el... done
All modules loaded successfully!

5.3.4. HTTP Inbox Fetch (Requires Server)

# Start server first
gmake server &
sleep 3

# Fetch inbox via Emacs batch mode
gmake elisp-http-inbox

Output:

Fetching inbox via HTTP from localhost:9898...
=== INBOX (5 waves) ===
  indexwave!indexwave: Wave Server Index
  localhost!conv+root: Discussion: Wave protocol features
  localhost!conv+root: Emacs client testing
  localhost!conv+root: Architecture discussion with diagram
  localhost!b+reply1: Charlie: Don't forget about the conflict resolution algorithms!

5.3.5. Batch Mode Elisp Code Example

The elisp-http-inbox target runs this code:

(require 'url)
(require 'json)

(defun fetch-inbox ()
  (with-current-buffer
    (url-retrieve-synchronously "http://localhost:9898/api/inbox" t)
    (goto-char (point-min))
    (re-search-forward "^$" nil t)
    (let* ((json-object-type 'plist)
           (inbox (json-read)))
      (message "=== INBOX (%d waves) ===" (length inbox))
      (dolist (wave (append inbox nil))
        (message "  %s: %s"
          (plist-get wave :id)
          (plist-get wave :digest))))))

(fetch-inbox)

5.4. WebSocket Testing

5.4.1. Python WebSocket Test Script

gmake ws-test

Uses scripts/ws-test.py to verify WebSocket connectivity:

Connecting to ws://localhost:9898/ws...
Connected!
Sending ProtocolOpenRequest for indexwave!indexwave
Received: {"version":0,"sequenceNumber":1,"messageType":"ProtocolWaveletUpdate",...}
All tests passed!

5.4.2. Interactive WebSocket Client

gmake ws-test-interactive

Opens an interactive session for manual WebSocket testing.

5.5. REST API Test Coverage

Endpoint Test Cases
GET /health Returns 200, contains status, contains timestamp
GET /api/inbox Returns list, items have required fields
GET /api/waves/{id} 404 for nonexistent, returns wavelet data
POST /api/waves/{id}/submit Add participant, remove participant, returns version
GET / Returns 200, contains version info
Error handling Invalid JSON returns 422, missing fields returns 422
Content types All endpoints return application/json

5.6. WebSocket Test Coverage

Category Test Cases
Connection lifecycle Connect success, clean disconnect, multiple connections
ProtocolOpenRequest Valid wave, nonexistent wave, multiple requests
Message handling Malformed JSON, empty message, invalid type, missing fields
Sequence numbers Response includes sequence, large sequence numbers
Concurrency Rapid messages (10 in sequence), interleaved connections

6. Progress Log

6.1. 2026-01-10: All Tests Passing

  • Fixed GET /api/waves/{id} 500 error
    • Added /health endpoint to wave_server.py
    • Fixed doc_data type handling (was list, expected dict)
  • Updated REST tests to use correct DeltaSubmission schema
  • Updated WebSocket tests for websockets 15+ API
  • Final Results: 32/32 tests passing

6.2. 2026-01-10: Extended Test Suite Created

  • Created tests/integration/test_rest_extended.py (18 tests)
  • Created tests/integration/test_ws_extended.py (14 tests)

6.3. 2026-01-08: WebSocket Tests Pass

  • Completed wave-client-for-emacs-x1z (WebSocket test task)
  • 8/8 WebSocket tests passing
  • Database seeded with 4 waves, 5 wavelets, 3 updates

6.4. 2026-01-07: Elisp Batch Mode

  • Added Makefile targets for headless testing
  • Fixed batch mode async issues using url-retrieve-synchronously

7. Issues & Resolutions

7.1. Resolved Issues

7.1.1. GET /api/waves/{id} Returns 500 [FIXED]

Problem: The waves endpoint had two issues:

  1. Missing /health endpoint
  2. doc_data was sometimes a list instead of dict

Solution: Added type checking in get_wave endpoint:

if isinstance(doc_data, dict):
    docs_dict[doc_id] = Document(
        doc_id=doc_id,
        contributors=doc_data.get("contributors", []),
        ...
    )
else:
    # doc_data is a primitive or list, wrap it as content
    docs_dict[doc_id] = Document(
        doc_id=doc_id,
        contributors=[],
        content=[doc_data] if not isinstance(doc_data, list) else doc_data
    )

7.1.2. WebSocket Tests Failing with websockets 15+ [FIXED]

Problem: The websockets library 15.0+ removed the .open attribute.

Solution: Use .state instead:

from websockets.protocol import State

def is_connected(ws) -> bool:
    return ws.state == State.OPEN

7.2. Known Limitations

7.2.1. Elisp Batch Mode Async

WebSocket operations in batch mode hit recursion limits due to accept-process-output loop depth. Use synchronous HTTP for batch testing.

7.2.2. FreeBSD Wheel Building

uv rebuilds C extensions on FreeBSD. Workaround: use polecat worktree venv which has pre-built packages.

8. Build Times (FreeBSD 14.3)

Operation Time
uv sync (cold) 2-5 min
uv sync (warm) <1 sec
pytest integration 10-30 sec
elisp byte-compile 5-10 sec
Server startup 2-3 sec

9. Completed Milestones

  • [X] Fix GET /api/waves/{id} 500 error
  • [X] Run extended REST test suite (18/18 passing)
  • [X] Run extended WebSocket test suite (14/14 passing)
  • [X] Emacs batch mode testing working
  • [X] Database seeding with test data

10. Next Steps

  1. [ ] Implement TUI dashboard demo
  2. [ ] Full E2E elisp integration test
  3. [ ] Document protocol compliance
  4. [ ] Add GitHub Actions CI workflow

11. References

12. Appendix: Beads Issue IDs

ID Type Priority Status Description
wave-client-for-emacs-4ez epic P1 active Full Integration Test Suite
wave-client-for-emacs-75v epic P1 active TUI Dashboard Demo
wave-client-for-emacs-vjm epic P1 active Wave Protocol Simulator
wave-client-for-emacs-802 task P2 completed Seed database
wave-client-for-emacs-x1z task P2 completed WebSocket tests
hq-jxx bug P2 open Google Code Archive missing downloads