SuperChat V1 Specification
Overview
This document defines the Minimum Viable Product (MVP) for SuperChat V1. The goal is to validate the core value proposition—threaded terminal chat with keyboard navigation—while keeping implementation complexity minimal.
V1 deliberately omits features that can be added later (V2+) without breaking compatibility. All deferred features are designed to be parallel additions that don't require V1 rewrites.
V1 Scope Summary
What V1 IS:
- Terminal-based threaded chat application
- Anonymous-only users (no registration)
- TCP connections with binary protocol
- Flat channel structure (no subchannels)
- Forum-style threading (all messages can be threaded)
- Real-time message broadcasts
- Keyboard-driven UI
- Client-side state persistence
What V1 is NOT:
- Not a secure messaging platform (no encryption in V1)
- Not multi-device synced (local state only)
- Not SSH-based (TCP only)
- Not hierarchical (no subchannels)
Core Features (V1)
1. Connection & Authentication
V1 Implementation:
- TCP connections only (no SSH)
- No user authentication (anonymous users)
- Server accepts connections without credentials
- Users set nickname on first message post (or proactively)
Connection Flow:
1. Client connects to server TCP port (default: 6465)
2. Server sends: SERVER_CONFIG (protocol version, limits)
3. Client validates protocol version, stores config
4. User is "connected anonymously (read-only)"
5. User browses channels without setting nickname
6. When user tries to post, client prompts for nickname
7. Client sends: SET_NICKNAME
8. Server responds: NICKNAME_RESPONSE
9. User can now post messages
V2 Migration Path:
- Protocol already supports AUTH_REQUEST/REGISTER_USER messages
- V1 server can ignore these or return "not implemented" error
- V2 adds User table and authentication logic
- V2 clients can detect server capabilities (check for USER_INFO message support)
2. Nickname Handling
V1 Implementation:
- Nicknames are not unique (multiple anonymous users can use same name)
- No password protection
- No persistent identity across disconnects
- Client stores last-used nickname locally, auto-sets on reconnect
Nickname Rules (V1):
- 3-20 characters
- Alphanumeric plus
-,_ - No spaces or special characters
- Case-insensitive uniqueness check (but V1 doesn't enforce uniqueness)
Display Convention:
- All anonymous users shown with
~prefix:~alice,~bob - Clients should visually distinguish multiple users with same name (future: add session ID suffix)
V2 Migration Path:
- V2 adds
User.registeredfield - Registered users shown without
~prefix:alice - Message table already has
author_user_id(null for V1 anonymous users) - No V1 data migration needed—anonymous messages remain with null user_id
3. Channel Structure
V1 Implementation:
- Flat channel list (no subchannel hierarchy)
- Channels are admin-created only (users cannot create channels in V1)
- Ship with 4 default channels:
#general- General discussion#tech- Technical topics#random- Off-topic chat#feedback- Bug reports and feature requests
Channel Properties (V1):
- All channels are forum type (threaded messages)
- Default retention: 7 days (168 hours)
- All channels are public (no private channels in V1)
V2 Migration Path:
- Protocol already supports CREATE_CHANNEL message
- Database has
Channel.created_byfield (nullable, null for admin-created) - V2 enables user channel creation, adds rate limiting
- Subchannels: Database has
Subchanneltable, protocol has CREATE_SUBCHANNEL- V1 server ignores subchannel messages or returns "not implemented"
- V2 adds subchannel logic without schema changes
4. Message Threading
V1 Implementation:
- All messages can be threaded (replies to replies)
- No depth limit - unlimited nesting supported
- Thread depth calculated on INSERT (parent.depth + 1)
- Stored in
Message.thread_depth(denormalized for performance)
Thread Display:
- Root messages (
parent_id = null, depth 0) shown in thread list - Each root shows reply count:
(12 replies) - Opening thread shows full tree (depth-first traversal)
- Indentation: 2 spaces per level
Threading Queries:
- Thread list:
SELECT * FROM Message WHERE channel_id = ? AND parent_id IS NULL ORDER BY created_at DESC LIMIT 50 - Thread view: Recursive query or depth-first sort (see implementation notes)
V2 Migration Path:
- No schema changes needed
- Chat channels (V2) still support threading, just UI de-emphasizes it
5. Message Operations
V1 Supported Operations:
- Post root message (start new thread)
- Reply to message (add to thread, up to depth 5)
- Soft-delete own message (sets
deleted_at, content →"[deleted]") - View messages (thread list and thread view)
NOT in V1:
- Edit message (deferred to V2)
- Hard delete (retention policy only)
- React to message (not planned)
- Pin message (deferred to V2)
Soft Delete Behavior:
- User can delete their own messages only (identified by session nickname)
- Message record remains (preserves thread structure)
- Content overwritten:
"[deleted]" - Original content saved to
MessageVersiontable (for moderation) - Deleted messages shown in thread as:
[deleted by ~alice]
V2 Migration Path:
- Message editing: Protocol has EDIT_MESSAGE, table has
edited_at- V1 server returns "not implemented" or ignores
- V2 adds edit logic + MessageVersion entries
- User-owned messages: V1 uses session nickname, V2 uses
author_user_id
6. Real-Time Updates
V1 Server Broadcasts:
NEW_MESSAGE: When anyone posts in a channel- Sent to all users currently in that channel
CHANNEL_CREATED: When admin creates new channel (V1: rare)- Sent to all connected users
SERVER_STATS: Periodic (every 30s) online user count- Sent to all connected users
MESSAGE_DELETED: When someone deletes a message- Sent to all users in that channel
Client Handling:
- Buffer broadcasts during message composition
- Show indicator:
(3 new messages)but don't update UI - Apply updates when user sends or cancels message
- Show indicator:
- Otherwise, apply updates immediately
- Append NEW_MESSAGE to thread list or thread view
- Update online user count
- Add new channels to channel list
V2 Migration Path:
- V2 adds more broadcast types:
MESSAGE_EDITED: Real-time edit notificationsSUBCHANNEL_CREATED: When subchannels addedUSER_JOINED/USER_LEFT: Presence notifications (optional)
- V1 clients ignore unknown message types (forward compatibility)
7. Read State Tracking
V1 Implementation:
- Client-side only (anonymous users, no server-side state)
- Client stores in local SQLite:
~/.config/superchat-client/state.db
Local State Schema:
CREATE TABLE ReadState (
channel_id INTEGER NOT NULL,
last_read_at INTEGER NOT NULL, -- Unix timestamp (milliseconds)
last_read_message_id INTEGER, -- Last message ID seen
PRIMARY KEY (channel_id)
);
Read State Updates:
- When user leaves channel: Update
last_read_atto current time - When user manually marks as read: Update to current time
- On reconnect: Load state from local DB
Unread Counts (V1):
- Option A (Recommended): No unread counts in V1
- Simplifies implementation
- User browses channels to check
- Option B: Client calculates locally
- Fetch recent messages on channel list load
- Count messages where
created_at > last_read_at - Network-heavy, but acceptable for MVP
V2 Migration Path:
- Server-side
UserChannelStatetable already defined - V2 adds
UPDATE_READ_STATEandGET_UNREAD_COUNTSmessages - Registered users get server-synced state (multi-device)
- Anonymous users continue using client-side (V1 behavior)
8. User Interface
V1 UI Design:
Layout:
┌──────────────────────────────────────────────────────────────────────┐
│ SuperChat v1.0 Connected: ~alice (anonymous) 23 users │
├────────────────┬─────────────────────────────────────────────────────┤
│ Channels │ #general - General Discussion │
│ ├─────────────────────────────────────────────────────┤
│ #general │ [Thread] How do I navigate? (4 replies) 5m ago │
│ #tech │ [Thread] Server uptime impressive (12) 2h ago │
│ #random │ [Thread] Can we get #programming? (0) 1d ago │
│ #feedback │ │
│ │ │
│ │ │
│ │ │
├────────────────┴─────────────────────────────────────────────────────┤
│ [↑↓] Navigate [Enter] Open [n] New Thread [h] Help [q] Quit │
└──────────────────────────────────────────────────────────────────────┘
Thread View:
┌──────────────────────────────────────────────────────────────────────┐
│ #general > Thread: How do I navigate? Connected: ~alice │
├──────────────────────────────────────────────────────────────────────┤
│ ~alice 5 minutes ago │
│ How do I navigate around SuperChat? │
│ │
│ ~bob 4 minutes ago │
│ Use arrow keys to move. Press [h] for help. │
│ │
│ ~charlie 2 minutes ago │
│ Also, [n] creates new thread, [r] replies. │
│ │
│ ~alice 1 minute ago │
│ Thanks! This is super helpful. │
│ │
│ ──────────────────────────────────────────────────────────────────── │
│ [Compose reply] Press 'r' to reply │
├──────────────────────────────────────────────────────────────────────┤
│ [Esc] Back [r] Reply [d] Delete [h] Help │
└──────────────────────────────────────────────────────────────────────┘
Key UI Principles:
- Always-visible footer with context-aware shortcuts
- Clear connection status in header (nickname + user count)
- Minimal visual clutter (borders, spacing, clear hierarchy)
- Relative timestamps ("5m ago", "2h ago", "1d ago")
- Anonymous indicator (
~prefix for all users in V1)
Keyboard Shortcuts:
| Key | Context | Action |
|---|---|---|
↑↓ |
Any | Navigate up/down |
←→ |
Channel list | Switch focus (channels ↔ threads) |
Enter |
Channel list | Join channel / Open thread |
n |
Thread list | New thread (compose root message) |
r |
Thread view | Reply to thread |
d |
Thread view | Delete own message (if selected) |
Esc |
Thread view / Modal | Back / Cancel |
h or ? |
Any | Show help modal |
q |
Any | Quit (with confirmation) |
First-Run Experience:
┌──────────────────────────────────────────────────────────────┐
│ Welcome to SuperChat v1.0 │
│ │
│ A terminal-based threaded chat application │
│ │
│ Getting Started: │
│ • Use arrow keys (↑↓←→) to navigate │
│ • Press [Enter] to select channels and threads │
│ • Press [h] anytime for help │
│ • Press [n] to start a new thread │
│ │
│ You're connected anonymously (read-only until you set │
│ a nickname). You'll be prompted when you post. │
│ │
│ [Press any key to continue] │
└──────────────────────────────────────────────────────────────┘
V2 Migration Path:
- UI adds registration prompt in footer:
[Ctrl+R] Register - Header shows registered users without
~:Connected: alice - Add visual indicators for encrypted DMs (lock icon or
[E]prefix)
9. Protocol & Data Model
V1 Protocol Messages (Subset):
Client → Server:
SET_NICKNAME(required before posting)LIST_CHANNELS(get public channel list)JOIN_CHANNEL(join to receive broadcasts)LEAVE_CHANNEL(stop receiving broadcasts)LIST_MESSAGES(fetch thread list or thread view)POST_MESSAGE(create root or reply)DELETE_MESSAGE(soft-delete own message)PING(keepalive, every 30s if idle)
Server → Client:
SERVER_CONFIG(sent on connect, protocol version + limits)NICKNAME_RESPONSE(success or error)CHANNEL_LIST(list of public channels)JOIN_RESPONSE(success or error)MESSAGE_LIST(thread list or thread replies)MESSAGE_POSTED(confirmation + message ID)NEW_MESSAGE(real-time broadcast)MESSAGE_DELETED(confirmation + broadcast)PONG(ping response)ERROR(generic error with code + message)
V1 NOT Implemented (Return ERROR):
AUTH_REQUEST→ Error 1001 "Authentication not supported in V1"REGISTER_USER→ Error 1001 "Registration not supported in V1"CREATE_CHANNEL→ Error 3000 "Permission denied (admin-only)"CREATE_SUBCHANNEL→ Error 1001 "Subchannels not supported in V1"EDIT_MESSAGE→ Error 1001 "Editing not supported in V1"START_DM→ Error 1001 "DMs not supported in V1"- All DM-related messages → Error 1001
V1 Database Schema (Minimal):
-- No User table in V1 (anonymous only)
CREATE TABLE Channel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
description TEXT,
channel_type INTEGER NOT NULL DEFAULT 1, -- 0=chat, 1=forum (V1: always 1)
message_retention_hours INTEGER NOT NULL DEFAULT 168, -- 7 days
created_by INTEGER, -- NULL for admin-created (V1: always NULL)
created_at INTEGER NOT NULL, -- Unix timestamp (ms)
is_private INTEGER NOT NULL DEFAULT 0 -- V1: always 0 (public only)
);
CREATE TABLE Session (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER, -- V1: always NULL (anonymous)
nickname TEXT NOT NULL,
connection_type TEXT NOT NULL, -- 'tcp' only in V1
connected_at INTEGER NOT NULL, -- Unix timestamp (ms)
last_activity INTEGER NOT NULL -- Unix timestamp (ms)
);
CREATE TABLE Message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL,
subchannel_id INTEGER, -- V1: always NULL (no subchannels)
parent_id INTEGER, -- NULL = root message
author_user_id INTEGER, -- V1: always NULL (anonymous)
author_nickname TEXT NOT NULL,
content TEXT NOT NULL,
created_at INTEGER NOT NULL, -- Unix timestamp (ms)
edited_at INTEGER, -- V1: always NULL (no editing)
deleted_at INTEGER, -- Soft delete timestamp
thread_depth INTEGER NOT NULL, -- 0-5
FOREIGN KEY (channel_id) REFERENCES Channel(id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES Message(id) ON DELETE CASCADE
);
CREATE TABLE MessageVersion (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL,
content TEXT NOT NULL,
author_nickname TEXT NOT NULL,
created_at INTEGER NOT NULL,
version_type TEXT NOT NULL, -- 'created', 'edited', 'deleted'
FOREIGN KEY (message_id) REFERENCES Message(id) ON DELETE CASCADE
);
-- Indexes
CREATE INDEX idx_messages_channel ON Message(channel_id, created_at DESC);
CREATE INDEX idx_messages_parent ON Message(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_messages_retention ON Message(created_at, parent_id);
CREATE INDEX idx_sessions_activity ON Session(last_activity);
V2 Migration Script:
-- Add User table (V2)
CREATE TABLE User (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nickname TEXT UNIQUE,
registered INTEGER NOT NULL DEFAULT 0,
password_hash TEXT,
allow_unencrypted_dms INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
last_seen INTEGER NOT NULL
);
-- Add SSHKey table (V2)
CREATE TABLE SSHKey (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
fingerprint TEXT UNIQUE NOT NULL,
public_key TEXT NOT NULL,
key_type TEXT NOT NULL,
can_encrypt INTEGER NOT NULL DEFAULT 0,
encryption_public_key TEXT,
added_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE
);
-- Add Subchannel table (V2)
CREATE TABLE Subchannel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL,
name TEXT NOT NULL,
display_name TEXT NOT NULL,
description TEXT,
message_retention_hours INTEGER NOT NULL DEFAULT 168,
subchannel_type INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
FOREIGN KEY (channel_id) REFERENCES Channel(id) ON DELETE CASCADE
);
-- Add ChannelAccess table (V2 - for private channels/DMs)
CREATE TABLE ChannelAccess (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
encryption_key TEXT,
added_at INTEGER NOT NULL,
FOREIGN KEY (channel_id) REFERENCES Channel(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE
);
-- Add UserChannelState table (V2 - server-side read state)
CREATE TABLE UserChannelState (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
channel_id INTEGER NOT NULL,
subchannel_id INTEGER,
last_read_at INTEGER NOT NULL,
last_read_message_id INTEGER,
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
FOREIGN KEY (channel_id) REFERENCES Channel(id) ON DELETE CASCADE,
FOREIGN KEY (subchannel_id) REFERENCES Subchannel(id) ON DELETE CASCADE
);
-- V1 data remains intact (Message.author_user_id stays NULL for anonymous messages)
-- No data migration needed
10. Server Implementation
V1 Server Responsibilities:
- Accept TCP connections on port 6465 (configurable)
- Parse binary protocol frames
- Validate protocol version (reject mismatches)
- Manage sessions (track connected clients)
- Store messages in SQLite database
- Broadcast real-time updates to relevant clients
- Enforce rate limits (10 messages/minute per session)
- Run retention cleanup job (hourly: delete old messages)
- Disconnect idle sessions (60s timeout)
Server Configuration (V1):
# ~/.config/superchat/config.toml
[server]
tcp_port = 6465
database_path = "~/.config/superchat/superchat.db"
[limits]
max_connections_per_ip = 10
message_rate_limit = 10 # messages per minute
max_message_length = 4096 # bytes
max_nickname_length = 20
session_timeout_seconds = 60
[retention]
default_retention_hours = 168 # 7 days
cleanup_interval_minutes = 60
[channels]
# V1: Admin-created only (seed channels on first run)
seed_channels = [
{ name = "general", description = "General discussion" },
{ name = "tech", description = "Technical topics" },
{ name = "random", description = "Off-topic chat" },
{ name = "feedback", description = "Bug reports and feature requests" }
]
V2 Migration Path:
- Add
[auth]section for password hashing (bcrypt rounds) - Add
[ssh]section for SSH server config (port, host keys) - Add
[dm]section for encryption settings - V1 config file is forward-compatible (V2 ignores unknown sections)
11. Client Implementation
V1 Client Responsibilities:
- Connect to server via TCP
- Parse binary protocol frames
- Render TUI (bubbletea + lipgloss)
- Handle keyboard input and navigation
- Store local state (nickname, read positions)
- Auto-reconnect on disconnect (exponential backoff)
- Buffer outgoing messages during disconnection
Client Configuration (V1):
# ~/.config/superchat-client/config.toml
[connection]
default_server = "chat.example.com"
default_port = 6465
auto_reconnect = true
reconnect_max_delay_seconds = 30
[local]
state_db = "~/.config/superchat-client/state.db"
last_nickname = "" # Auto-filled on first run
auto_set_nickname = true # Use last_nickname on connect
[ui]
show_timestamps = true
timestamp_format = "relative" # 'relative' or 'absolute'
theme = "default" # For future themes
Client State Database:
-- ~/.config/superchat-client/state.db
CREATE TABLE Config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE ReadState (
channel_id INTEGER PRIMARY KEY,
last_read_at INTEGER NOT NULL,
last_read_message_id INTEGER
);
INSERT INTO Config (key, value) VALUES ('last_nickname', '');
V2 Migration Path:
- Client detects server capabilities (check SERVER_CONFIG or probe with AUTH_REQUEST)
- If server supports registration: Show
[Ctrl+R] Registerin footer - If server supports SSH: Add SSH connection option in connect modal
- Client state remains local for anonymous mode, syncs to server for registered users
V2 Feature Roadmap
V2 Additions (No V1 Breaking Changes):
User Registration & Authentication
- Add User table, password hashing
- AUTH_REQUEST/REGISTER_USER messages enabled
- Server-side read state sync (UserChannelState)
- Registered users shown without
~prefix
SSH Connections
- Add SSH server (port 2222)
- SSHKey table for key authentication
- Auto-registration on first SSH connect
- Seamless authentication for registered users
Subchannels
- Add Subchannel table
- CREATE_SUBCHANNEL message enabled
- Two-level hierarchy UI (channels with subchannels)
- Channel-level vs subchannel-level posting
Message Editing
- EDIT_MESSAGE message enabled
- MessageVersion tracks edits
- Show
(edited)indicator in UI - Edit history available to moderators
User-Created Channels
- CREATE_CHANNEL message enabled (authenticated users only)
- Rate limiting (5 channels/hour)
- Channel operators (created_by field)
V3 Additions (Future):
Direct Messages
- Private channels (is_private = true)
- ChannelAccess table for participants
- START_DM flow (complex, deferred)
Encryption (with DMs)
- Hybrid encryption (RSA + AES-256-GCM)
- Key management (PROVIDE_PUBLIC_KEY)
- Encrypted payload flag support
Chat Channel Type
- channel_type = 0 (chat) enabled
- UI emphasizes chronological flow
- Shorter default retention (1 hour)
Compression
- LZ4 compression for large payloads
- Flags byte bit 0 support
- Automatic compression for >512 byte payloads
Architecture Principles (V1 → V2 Compatibility)
Design Decisions That Enable Parallel V2 Development:
Protocol Extensibility
- Message types 0x01-0x14 defined but not all implemented in V1
- V1 server returns ERROR 1001 "Not implemented" for V2 messages
- V1 clients ignore unknown server message types (forward compatibility)
- Flags byte has reserved bits for future features
Database Forward Compatibility
- Tables have nullable FK fields for V2 features (e.g.,
author_user_id,subchannel_id) - V1 leaves these NULL, V2 populates them
- No schema migration needed for V1 → V2 upgrade
- V2 tables (User, SSHKey, etc.) are additive (no changes to V1 tables)
- Tables have nullable FK fields for V2 features (e.g.,
Client Capability Detection
- Clients probe server for feature support (send request, check for ERROR 1001)
- Server sends SERVER_CONFIG with capabilities (could extend in V2)
- Clients adapt UI based on server version (hide unavailable features)
State Separation
- V1 state is client-side (no server-side state beyond sessions)
- V2 adds server-side state (UserChannelState) for registered users
- Anonymous users continue using client-side state (V1 behavior)
- No conflict or migration needed
Identity Model
- V1 messages have
author_user_id = NULL(anonymous) - V2 adds
author_user_id != NULLfor registered users - Both coexist in same Message table
- UI distinguishes:
~alice(anonymous) vsalice(registered)
- V1 messages have
Critical: What NOT to Do in V1
- ❌ Don't hardcode protocol version checks (allow version negotiation)
- ❌ Don't use fixed-size message structures (use length-prefixed fields)
- ❌ Don't tie UI to anonymous-only (design for both anonymous and registered)
- ❌ Don't store session-specific data in database (use in-memory for V1)
- ❌ Don't couple channel display to flat structure (prepare for hierarchy)
Success Criteria (V1)
V1 is successful if:
- ✅ First-time user can post within 2 minutes (no confusion)
- ✅ Threading works intuitively (users engage with nested replies)
- ✅ Keyboard navigation is smooth (no "how do I...?" questions)
- ✅ Real-time updates work (users see messages without refreshing)
- ✅ Client auto-reconnects reliably (handles network drops)
- ✅ Users return (client remembers nickname, state persists)
- ✅ Feedback requests V2 features (registration, SSH, DMs)
- ✅ No critical UX bugs (errors are clear, recovery is obvious)
Metrics to Track:
- Time to first post (target: <2 minutes)
- Thread depth distribution (are users replying deeply?)
- Session duration (do users stick around?)
- Reconnection success rate (>95%)
- Error rate (nickname conflicts, rate limits, etc.)
- Feature requests (prioritize V2 roadmap)
Testing Strategy (V1)
Testing Stack
Dependencies:
require (
// Production dependencies...
// Testing (test-only)
github.com/stretchr/testify v1.8.4 // Assertions and test utilities
pgregory.net/rapid v1.1.0 // Property-based testing
github.com/jandelgado/gcov2lcov latest // Coverage format conversion
)
Built-in Go Tools:
- Standard
testingpackage - Go 1.18+ fuzzing (
-fuzzflag) - Race detector (
-raceflag) - Coverage analysis (
-coverprofile)
Coverage Requirements
Protocol Package (pkg/protocol/*):
- Target: 100% coverage (enforced in CI)
- Every line, branch, and edge case tested
- Build fails if protocol coverage < 100%
Other Packages:
- Server (
pkg/server/*): 80-90% coverage - Client (
pkg/client/*): 70-80% coverage - Integration tests: Key user flows covered
Coverage Output Format:
- Generate 3 separate lcov files for coverage service:
protocol.lcov- Protocol package (100% required)server.lcov- Server package (80-90% target)client.lcov- Client package (70-80% target)
Test Types
Unit Tests (Table-Driven):
- Protocol frame encoding/decoding (all message types)
- Message threading depth calculation
- Read state updates (client-side)
- Retention policy logic
- Database operations (in-memory SQLite)
- Handler functions (mock sessions)
Property-Based Tests (Rapid):
- Frame round-trip serialization (any valid frame)
- String encoding/decoding (any valid UTF-8)
- Timestamp handling (any valid time)
- Optional field handling (present/nil values)
Fuzzing Tests:
- Frame decoder (malformed binary input)
- Message decoders (invalid payloads)
- String decoder (length attacks, invalid UTF-8)
- Ensures robustness against malicious input
Integration Tests:
- TCP connection handling
- Real-time message broadcasts (multi-client)
- Rate limiting enforcement
- Session timeout cleanup
- Auto-reconnect logic
Manual Tests:
- First-run user experience (start to post)
- Keyboard navigation (all shortcuts)
- Thread display (nested replies, indentation)
- Connection drop and reconnect
- Terminal resize handling
Load Tests (Optional for V1):
- 100 concurrent users
- 1000 messages/minute broadcast
- Memory usage over 24 hours
Makefile
Create Makefile in project root:
.PHONY: test coverage coverage-html coverage-lcov coverage-protocol coverage-summary fuzz clean
# Run all tests
test:
go test ./... -race
# Generate coverage for all packages (combined)
coverage:
go test ./... -coverprofile=coverage.out -covermode=atomic
go tool cover -func=coverage.out
# Generate HTML coverage report (combined)
coverage-html:
go test ./... -coverprofile=coverage.out -covermode=atomic
go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report generated: coverage.html"
# Generate separate lcov files per package
coverage-lcov:
@echo "Generating coverage for protocol package..."
go test ./pkg/protocol/... -coverprofile=protocol.out -covermode=atomic
gcov2lcov -infile=protocol.out -outfile=protocol.lcov
@echo "Generating coverage for server package..."
go test ./pkg/server/... -coverprofile=server.out -covermode=atomic
gcov2lcov -infile=server.out -outfile=server.lcov
@echo "Generating coverage for client package..."
go test ./pkg/client/... -coverprofile=client.out -covermode=atomic
gcov2lcov -infile=client.out -outfile=client.lcov
@echo ""
@echo "LCOV coverage reports generated:"
@echo " - protocol.lcov (pkg/protocol)"
@echo " - server.lcov (pkg/server)"
@echo " - client.lcov (pkg/client)"
# Check protocol coverage (must be 100%)
coverage-protocol:
go test ./pkg/protocol/... -coverprofile=protocol.out -covermode=atomic
@COVERAGE=$$(go tool cover -func=protocol.out | grep total | awk '{print $$3}' | sed 's/%//'); \
echo "Protocol coverage: $$COVERAGE%"; \
if [ $$(echo "$$COVERAGE < 100" | bc -l) -eq 1 ]; then \
echo "ERROR: Protocol coverage must be 100%"; \
exit 1; \
fi
# Show coverage summary for each package
coverage-summary:
@echo "=== Protocol Coverage ==="
@go tool cover -func=protocol.out | grep total || echo "Run 'make coverage-lcov' first"
@echo ""
@echo "=== Server Coverage ==="
@go tool cover -func=server.out | grep total || echo "Run 'make coverage-lcov' first"
@echo ""
@echo "=== Client Coverage ==="
@go tool cover -func=client.out | grep total || echo "Run 'make coverage-lcov' first"
# Run fuzzing
fuzz:
go test ./pkg/protocol -fuzz=FuzzDecodeFrame -fuzztime=5m
# Clean coverage files
clean:
rm -f coverage.out coverage.html
rm -f protocol.out protocol.lcov
rm -f server.out server.lcov
rm -f client.out client.lcov
Usage:
# Generate all 3 lcov files for coverage service
make coverage-lcov
# Check protocol has 100% coverage (fails if not)
make coverage-protocol
# View summary of all packages
make coverage-summary
# Run fuzzing tests
make fuzz
# View HTML coverage locally
make coverage-html
open coverage.html
CI Configuration
Create .github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.21', '1.22']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}
- name: Install gcov2lcov
run: go install github.com/jandelgado/gcov2lcov@latest
- name: Run all tests
run: go test ./... -race
- name: Generate protocol coverage
run: |
go test ./pkg/protocol/... -coverprofile=protocol.out -covermode=atomic
gcov2lcov -infile=protocol.out -outfile=protocol.lcov
- name: Generate server coverage
run: |
go test ./pkg/server/... -coverprofile=server.out -covermode=atomic
gcov2lcov -infile=server.out -outfile=server.lcov
- name: Generate client coverage
run: |
go test ./pkg/client/... -coverprofile=client.out -covermode=atomic
gcov2lcov -infile=client.out -outfile=client.lcov
- name: Check protocol coverage (100% required)
run: |
COVERAGE=$(go tool cover -func=protocol.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Protocol coverage: $COVERAGE%"
if (( $(echo "$COVERAGE < 100" | bc -l) )); then
echo "ERROR: Protocol coverage is $COVERAGE%, must be 100%"
exit 1
fi
- name: Display coverage summary
run: |
echo "=== Protocol Coverage ==="
go tool cover -func=protocol.out | grep total
echo ""
echo "=== Server Coverage ==="
go tool cover -func=server.out | grep total
echo ""
echo "=== Client Coverage ==="
go tool cover -func=client.out | grep total
- name: Run fuzzing (short)
run: go test ./pkg/protocol -fuzz=FuzzDecodeFrame -fuzztime=30s
- name: Upload coverage files
run: |
# Upload to your coverage service here
# Example:
# curl -X POST -F "protocol=@protocol.lcov" https://coverage-service.com/upload
# curl -X POST -F "server=@server.lcov" https://coverage-service.com/upload
# curl -X POST -F "client=@client.lcov" https://coverage-service.com/upload
echo "Upload protocol.lcov, server.lcov, client.lcov to coverage service"
- name: Archive coverage artifacts
uses: actions/upload-artifact@v3
with:
name: coverage-${{ matrix.os }}-go${{ matrix.go }}
path: |
protocol.out
protocol.lcov
server.out
server.lcov
client.out
client.lcov
.gitignore Additions
Add to .gitignore:
# Coverage files
coverage.out
coverage.html
protocol.out
protocol.lcov
server.out
server.lcov
client.out
client.lcov
*.coverprofile
# Build artifacts
superchat-server
superchat
superchat-client
*.exe
*.dll
*.so
*.dylib
# Test binaries
*.test
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
---
## Deployment (V1)
**Server Deployment:**
```bash
# Build server
go build -o superchat-server cmd/server/main.go
# Run server (creates default config if missing)
./superchat-server
# Or with custom config
./superchat-server --config /path/to/config.toml
# Systemd service (optional)
sudo systemctl enable superchat-server
sudo systemctl start superchat-server
Client Installation:
# Build client
go build -o superchat cmd/client/main.go
# Run client
./superchat connect chat.example.com:6465
# Or with default server from config
./superchat connect
Firewall Rules:
- Open TCP port 6465 (configurable)
- No TLS in V1 (plaintext TCP, add TLS in V2 or use SSH tunnel)
Open Questions (V1)
Resolved:
- ✅ Nickname uniqueness? No (anonymous users can duplicate)
- ✅ Thread depth limit? No limit (unlimited nesting supported)
- ✅ Unread counts? No (defer to V2 for simplicity)
- ✅ User-created channels? No (admin-only in V1)
- ✅ Message editing? No (defer to V2)
Still Open:
- Should V1 show timestamps on every message, or just relative time on hover?
- Recommendation: Show relative timestamps always ("5m ago")
- Should client support multiple server connections (switch between servers)?
- Recommendation: No, single server in V1. V2 can add server switcher.
- Should V1 have a splash screen on every launch or just first run?
- Recommendation: Splash on first run only, skip on subsequent launches.
- Should client buffer outgoing messages during disconnect, or discard?
- Recommendation: Buffer in memory (up to 100 messages), send on reconnect.
Implementation Phases (V1)
Phase 1: Core Protocol & Database (Week 1-2)
- Binary protocol frame parser (encode/decode) with EncodeTo(io.Writer) architecture
- Protocol tests (83.6% coverage achieved, 100% on critical paths)
- Unit tests for all 13 message types (table-driven)
- Encode/Decode round-trip tests
- Error path tests with failing writers
- Edge cases (max sizes, empty values, null optionals, validation errors)
- Property-based tests (rapid) - deferred to Phase 4
- Fuzzing tests - deferred to Phase 4
- SQLite schema setup (Channel, Session, Message tables)
- TCP server (accept connections, parse frames)
- Basic message handlers (SET_NICKNAME, LIST_CHANNELS, LIST_MESSAGES)
- Handler unit tests (mock sessions)
Phase 2: Message Operations (Week 2-3)
- POST_MESSAGE handler (create root and replies)
- Threading logic (depth calculation, parent validation)
- DELETE_MESSAGE handler (soft-delete)
- Real-time broadcast (NEW_MESSAGE to all sessions in channel)
- Database operation tests (in-memory SQLite)
- Integration tests (multi-client broadcasts)
Phase 3: Client TUI (Week 3-4)
- Bubbletea app structure (models, update, view)
- Channel list view (sidebar + main pane)
- Thread list view (root messages with reply counts)
- Thread view (nested replies with indentation)
- Keyboard navigation (arrow keys, Enter, Esc)
- Client state tests (local read state)
- UI state transition tests
Phase 4: Polish & Testing Infrastructure (Week 4-5)
- Message composition modal (new thread, reply)
- Nickname prompt (inline, before first post)
- Help modal (keyboard shortcuts)
- Auto-reconnect logic (exponential backoff)
- Local state persistence (nickname, read positions)
- First-run splash screen
- Error handling (toasts, clear messages)
- Testing infrastructure (completed early)
- Makefile with coverage targets (test, coverage, coverage-lcov)
- .gitignore for coverage files (.out, .lcov, .coverprofile)
- Generate 3 lcov files (protocol, server, client)
- CI configuration (.github/workflows/ci.yml)
- Property-based tests (rapid) from Phase 1
- Fuzzing tests from Phase 1
Phase 5: Deployment & Documentation (Week 5-6)
- Server config file (TOML parsing)
- Client config file (TOML parsing)
- Retention cleanup job (cron-like background task)
- Session timeout cleanup
- Database migration framework (CRITICAL for V1→V2 upgrade)
- Schema versioning (store schema_version in database)
- Migration runner (apply SQL migrations in order)
- Migration files (numbered: 001_initial.sql, 002_add_thread_root.sql, etc.)
- Automatic migration on server startup (check version, apply pending)
- Rollback support (down migrations for each up migration)
- Migration testing (verify V1→V2 upgrade path works)
- Build scripts (cross-compile for Linux/macOS/Windows)
- README with setup instructions
- User documentation (keyboard shortcuts, FAQ)
- Final testing pass
- Manual testing (all user flows)
- Load testing (optional, 100 concurrent users)
- Coverage summary verification
- Migration testing (V1 DB → apply all migrations → verify schema)
Total: ~6 weeks for V1 MVP
Files Structure
superchat/
├── cmd/
│ ├── server/
│ │ └── main.go # Server entry point
│ └── client/
│ └── main.go # Client entry point
├── pkg/
│ ├── protocol/
│ │ ├── frame.go # Frame encoding/decoding
│ │ ├── frame_test.go # Frame tests (100% coverage)
│ │ ├── messages.go # Message type definitions
│ │ ├── messages_test.go # Message tests (100% coverage)
│ │ ├── types.go # Primitive type serialization
│ │ ├── types_test.go # Type tests (100% coverage)
│ │ ├── rapid_test.go # Property-based tests
│ │ └── fuzz_test.go # Fuzzing tests
│ ├── server/
│ │ ├── server.go # TCP server
│ │ ├── server_test.go # Server tests
│ │ ├── session.go # Session management
│ │ ├── handlers.go # Message handlers
│ │ ├── handlers_test.go # Handler tests
│ │ ├── broadcast.go # Real-time broadcasts
│ │ ├── broadcast_test.go # Broadcast tests
│ │ ├── db.go # Database operations
│ │ ├── db_test.go # Database tests
│ │ └── integration_test.go # Multi-client integration tests
│ ├── client/
│ │ ├── client.go # TCP client
│ │ ├── client_test.go # Client tests
│ │ ├── state.go # Local state management
│ │ ├── state_test.go # State tests
│ │ └── ui/
│ │ ├── app.go # Bubbletea app
│ │ ├── app_test.go # App state tests
│ │ ├── channels.go # Channel list view
│ │ ├── threads.go # Thread list view
│ │ ├── messages.go # Thread view
│ │ └── compose.go # Composition modal
│ └── config/
│ ├── server.go # Server config parsing
│ ├── server_test.go # Config tests
│ ├── client.go # Client config parsing
│ └── client_test.go # Config tests
├── .github/
│ └── workflows/
│ └── ci.yml # CI configuration
├── docs/
│ ├── PROTOCOL.md # Full protocol spec
│ ├── IMPLEMENTATION_PLAN.md # Overall implementation plan
│ ├── DATA_MODEL.md # Database schema and data model
│ └── V1.md # This document (V1 spec)
├── Makefile # Build and test commands
├── .gitignore # Git ignore (includes coverage files)
├── go.mod # Go module definition
├── go.sum # Go dependency checksums
└── README.md # Project overview and setup
Summary
V1 is a minimal, functional, threaded chat application that validates the core UX (threading + keyboard navigation) while keeping complexity low. All V2 features (registration, SSH, subchannels, DMs, encryption) are designed as parallel additions that don't require V1 rewrites.
Key V1 Constraints:
- Anonymous users only
- TCP connections only
- Flat channels (no subchannels)
- Forum threading (no chat type)
- Client-side state (no server sync)
- Admin-created channels (no user creation)
Key V2 Enablers:
- Protocol messages defined but not implemented (return "not implemented")
- Database fields nullable for V2 features (e.g.,
author_user_id) - Client capability detection (probe server, adapt UI)
- Additive schema (V2 tables don't change V1 tables)
Success = Users love threading and want more features (registration, DMs, etc.)