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.registered field
  • 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_by field (nullable, null for admin-created)
  • V2 enables user channel creation, adds rate limiting
  • Subchannels: Database has Subchannel table, 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:

  1. Post root message (start new thread)
  2. Reply to message (add to thread, up to depth 5)
  3. Soft-delete own message (sets deleted_at, content → "[deleted]")
  4. 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 MessageVersion table (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
  • 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 notifications
    • SUBCHANNEL_CREATED: When subchannels added
    • USER_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_at to 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 UserChannelState table already defined
  • V2 adds UPDATE_READ_STATE and GET_UNREAD_COUNTS messages
  • 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:

  1. Always-visible footer with context-aware shortcuts
  2. Clear connection status in header (nickname + user count)
  3. Minimal visual clutter (borders, spacing, clear hierarchy)
  4. Relative timestamps ("5m ago", "2h ago", "1d ago")
  5. 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] Register in 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):

  1. 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
  2. SSH Connections

    • Add SSH server (port 2222)
    • SSHKey table for key authentication
    • Auto-registration on first SSH connect
    • Seamless authentication for registered users
  3. Subchannels

    • Add Subchannel table
    • CREATE_SUBCHANNEL message enabled
    • Two-level hierarchy UI (channels with subchannels)
    • Channel-level vs subchannel-level posting
  4. Message Editing

    • EDIT_MESSAGE message enabled
    • MessageVersion tracks edits
    • Show (edited) indicator in UI
    • Edit history available to moderators
  5. User-Created Channels

    • CREATE_CHANNEL message enabled (authenticated users only)
    • Rate limiting (5 channels/hour)
    • Channel operators (created_by field)

V3 Additions (Future):

  1. Direct Messages

    • Private channels (is_private = true)
    • ChannelAccess table for participants
    • START_DM flow (complex, deferred)
  2. Encryption (with DMs)

    • Hybrid encryption (RSA + AES-256-GCM)
    • Key management (PROVIDE_PUBLIC_KEY)
    • Encrypted payload flag support
  3. Chat Channel Type

    • channel_type = 0 (chat) enabled
    • UI emphasizes chronological flow
    • Shorter default retention (1 hour)
  4. 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:

  1. 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
  2. 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)
  3. 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)
  4. 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
  5. Identity Model

    • V1 messages have author_user_id = NULL (anonymous)
    • V2 adds author_user_id != NULL for registered users
    • Both coexist in same Message table
    • UI distinguishes: ~alice (anonymous) vs alice (registered)

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:

  1. ✅ First-time user can post within 2 minutes (no confusion)
  2. ✅ Threading works intuitively (users engage with nested replies)
  3. ✅ Keyboard navigation is smooth (no "how do I...?" questions)
  4. ✅ Real-time updates work (users see messages without refreshing)
  5. ✅ Client auto-reconnects reliably (handles network drops)
  6. ✅ Users return (client remembers nickname, state persists)
  7. ✅ Feedback requests V2 features (registration, SSH, DMs)
  8. ✅ 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 testing package
  • Go 1.18+ fuzzing (-fuzz flag)
  • Race detector (-race flag)
  • 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:

  1. Should V1 show timestamps on every message, or just relative time on hover?
    • Recommendation: Show relative timestamps always ("5m ago")
  2. Should client support multiple server connections (switch between servers)?
    • Recommendation: No, single server in V1. V2 can add server switcher.
  3. Should V1 have a splash screen on every launch or just first run?
    • Recommendation: Splash on first run only, skip on subsequent launches.
  4. 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.)