diff --git a/.agents/summary/.last_commit b/.agents/summary/.last_commit
new file mode 100644
index 0000000..ef0474f
--- /dev/null
+++ b/.agents/summary/.last_commit
@@ -0,0 +1 @@
+f69355d69d25687624c22c441aaf9fd12e20140b
diff --git a/.agents/summary/architecture.md b/.agents/summary/architecture.md
new file mode 100644
index 0000000..6cdf0de
--- /dev/null
+++ b/.agents/summary/architecture.md
@@ -0,0 +1,134 @@
+# Architecture
+
+## System Architecture Overview
+
+```mermaid
+graph TB
+ subgraph Clients
+ C1[WebSocket Client 1]
+ C2[WebSocket Client 2]
+ C3[WebSocket Client N]
+ end
+
+ subgraph "WebSocket Relay Server"
+ EP[HTTP/TLS Endpoint]
+ HUB[Hub - Connection Manager]
+ BC[Broadcast Channel]
+ MET[Prometheus Metrics]
+ end
+
+ subgraph Monitoring
+ PROM[Prometheus Scraper]
+ end
+
+ C1 -->|ws/wss| EP
+ C2 -->|ws/wss| EP
+ C3 -->|ws/wss| EP
+ EP --> HUB
+ HUB --> BC
+ BC -->|relay to all| C1
+ BC -->|relay to all| C2
+ BC -->|relay to all| C3
+ HUB --> MET
+ MET -->|:9090/metrics| PROM
+```
+
+## Design Pattern: Hub-and-Spoke
+
+The application uses a **Hub-and-Spoke** (fan-out) pattern where:
+
+1. **Hub** is the central coordinator managing all WebSocket connections
+2. **Spokes** are individual WebSocket client connections
+3. Every message received from any client is **broadcast to all connected clients**
+
+```mermaid
+graph LR
+ subgraph Hub
+ REG[Register Channel]
+ UNREG[Unregister Channel]
+ BCAST[Broadcast Channel]
+ CLIENTS[Client Map]
+ end
+
+ CONN[New Connection] --> REG
+ REG --> CLIENTS
+ DISC[Disconnection] --> UNREG
+ UNREG --> CLIENTS
+ MSG[Incoming Message] --> BCAST
+ BCAST --> CLIENTS
+```
+
+## Concurrency Model
+
+The server uses Go's CSP (Communicating Sequential Processes) concurrency model:
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Handler as HTTP Handler
+ participant Hub as Hub.Run() goroutine
+ participant Reader as ReadMessage goroutine
+
+ Client->>Handler: HTTP Upgrade Request
+ Handler->>Hub: register <- conn
+ Hub->>Hub: Add to clients map
+ Handler->>Reader: Start goroutine
+
+ loop Read Messages
+ Client->>Reader: WebSocket Frame
+ Reader->>Hub: broadcast <- message
+ Hub->>Hub: Iterate clients map
+ Hub->>Client: WriteMessage (fan-out)
+ end
+
+ Reader->>Hub: unregister <- conn (on error/close)
+ Hub->>Hub: Remove from clients map
+```
+
+### Goroutine Lifecycle
+
+| Goroutine | Purpose | Lifetime |
+|-----------|---------|----------|
+| `main` | HTTP server, accepts connections | Application lifetime |
+| `Hub.Run()` | Processes register/unregister/broadcast channels | Application lifetime |
+| Per-client reader | Reads messages from a single client | Client connection lifetime |
+| Metrics server | Serves `/metrics` endpoint | Application lifetime (if enabled) |
+
+## Configuration Architecture
+
+```mermaid
+graph LR
+ CLI[CLI Flag: --config-file] --> LOAD[config.Load]
+ LOAD --> YAML[YAML Parser]
+ YAML --> CFG[Config Struct]
+ CFG --> SRV[Server Setup]
+ CFG --> TLS[TLS Config]
+ CFG --> MET[Metrics Setup]
+```
+
+## Security Model
+
+- **TLS Support**: Optional TLS via cert/key PEM files
+- **Origin Check**: `CheckOrigin` allows all origins (permissive for relay use case)
+- **No Authentication**: The relay is designed as a transparent message forwarder
+- **No Authorization**: All connected clients can send/receive all messages
+
+## Deployment Architecture
+
+```mermaid
+graph TB
+ subgraph "Build Pipeline"
+ SRC[Source Code] --> CI[Gitea CI]
+ CI --> TEST[go test]
+ CI --> LINT[golangci-lint]
+ TAG[Git Tag v*] --> REL[Release Pipeline]
+ REL --> BIN_L[Linux amd64 Binary]
+ REL --> BIN_M[macOS arm64 Binary]
+ end
+
+ subgraph "Runtime"
+ BIN[Binary] --> CFG[config.yaml]
+ CFG --> SERVER[WebSocket Server :8443]
+ CFG --> METRICS[Metrics Server :9090]
+ end
+```
diff --git a/.agents/summary/codebase_info.md b/.agents/summary/codebase_info.md
new file mode 100644
index 0000000..73e5c45
--- /dev/null
+++ b/.agents/summary/codebase_info.md
@@ -0,0 +1,74 @@
+# Codebase Information
+
+## Project Overview
+
+| Field | Value |
+|-------|-------|
+| **Name** | websocket-relay |
+| **Language** | Go 1.21 |
+| **Type** | WebSocket relay server |
+| **License** | Not specified |
+| **Repository** | Gitea-hosted |
+
+## Directory Structure
+
+```
+websocket-relay/
+├── main.go # Application entry point
+├── go.mod # Go module definition
+├── go.sum # Dependency checksums
+├── config.yaml # Runtime configuration
+├── config.example.yaml # Example configuration with TLS enabled
+├── Makefile # Build, test, release commands
+├── cert.pem # TLS certificate (local dev)
+├── key.pem # TLS private key (local dev)
+├── README.md # Project readme
+├── .gitignore # Git ignore rules
+├── example/
+│ └── index.html # Browser-based P2P chat demo
+├── internal/
+│ ├── config/
+│ │ ├── config.go # YAML configuration loader
+│ │ └── config_test.go # Config loader tests
+│ ├── hub/
+│ │ ├── hub.go # WebSocket hub (connection management + broadcast)
+│ │ └── hub_test.go # Hub unit tests
+│ └── metrics/
+│ └── metrics.go # Prometheus metrics definitions
+└── .gitea/
+ └── workflows/
+ ├── ci.yml # CI pipeline (test + lint)
+ └── release.yml # Release pipeline (build + publish)
+```
+
+## Technology Stack
+
+| Category | Technology | Version |
+|----------|-----------|---------|
+| Runtime | Go | 1.21 |
+| WebSocket | gorilla/websocket | 1.5.1 |
+| Metrics | prometheus/client_golang | 1.17.0 |
+| Configuration | gopkg.in/yaml.v3 | 3.0.1 |
+| CI/CD | Gitea Actions | — |
+| Linting | golangci-lint | latest |
+
+## Build Targets
+
+| Command | Description |
+|---------|-------------|
+| `make build` | Build binary to `build/websocket-relay` |
+| `make test` | Run all tests with verbose output |
+| `make release` | Cross-compile for linux/amd64 and darwin/arm64 |
+| `make clean` | Remove build artifacts |
+| `make run` | Run from source |
+| `make deps` | Tidy Go modules |
+
+## Key Metrics
+
+| Metric | Value |
+|--------|-------|
+| Total Go files | 5 (+ 2 test files) |
+| Packages | 4 (`main`, `config`, `hub`, `metrics`) |
+| Test files | 2 |
+| CI Pipelines | 2 (CI + Release) |
+| External dependencies | 3 direct, 9 indirect |
diff --git a/.agents/summary/components.md b/.agents/summary/components.md
new file mode 100644
index 0000000..123567a
--- /dev/null
+++ b/.agents/summary/components.md
@@ -0,0 +1,147 @@
+# Components
+
+## Component Overview
+
+```mermaid
+graph TB
+ subgraph "main (Entry Point)"
+ MAIN[main.go]
+ end
+
+ subgraph "internal/config"
+ CFG[Config Loader]
+ end
+
+ subgraph "internal/hub"
+ HUB[Hub Manager]
+ WS[WebSocket Handler]
+ end
+
+ subgraph "internal/metrics"
+ MET[Prometheus Metrics]
+ end
+
+ MAIN --> CFG
+ MAIN --> HUB
+ MAIN --> MET
+ HUB --> WS
+ HUB --> MET
+```
+
+## Package: `main`
+
+**File:** `main.go`
+
+**Responsibility:** Application entry point and server initialization.
+
+**Behavior:**
+1. Parses CLI flags (`--config-file`)
+2. Loads YAML configuration
+3. Creates and starts the Hub
+4. Optionally starts the metrics HTTP server on a separate port
+5. Starts the WebSocket HTTP/TLS server
+
+**Dependencies:** `internal/config`, `internal/hub`, `prometheus/client_golang`
+
+---
+
+## Package: `internal/hub`
+
+**File:** `internal/hub/hub.go`
+
+**Responsibility:** WebSocket connection lifecycle management and message broadcasting.
+
+### Struct: `Hub`
+
+| Field | Type | Purpose |
+|-------|------|---------|
+| `clients` | `map[*websocket.Conn]bool` | Set of active connections |
+| `broadcast` | `chan []byte` | Channel for messages to relay |
+| `register` | `chan *websocket.Conn` | Channel for new connections |
+| `unregister` | `chan *websocket.Conn` | Channel for disconnections |
+| `mu` | `sync.RWMutex` | Protects the clients map |
+
+### Methods
+
+| Method | Signature | Description |
+|--------|-----------|-------------|
+| `New` | `func New() *Hub` | Constructor, initializes all channels and map |
+| `Run` | `func (h *Hub) Run()` | Main event loop processing channels (blocking) |
+| `HandleWebSocket` | `func (h *Hub) HandleWebSocket(w, r)` | HTTP handler — upgrades connection and starts reader |
+| `ClientCount` | `func (h *Hub) ClientCount() int` | Returns current connected client count (thread-safe) |
+
+### Connection Flow
+
+```mermaid
+stateDiagram-v2
+ [*] --> HTTPRequest: Client connects
+ HTTPRequest --> Upgraded: WebSocket upgrade
+ Upgraded --> Registered: register channel
+ Registered --> Reading: goroutine loop
+ Reading --> Broadcasting: message received
+ Broadcasting --> Reading: continue
+ Reading --> Unregistered: error/close
+ Unregistered --> [*]: connection cleaned up
+```
+
+---
+
+## Package: `internal/config`
+
+**File:** `internal/config/config.go`
+
+**Responsibility:** YAML configuration file loading and parsing.
+
+### Struct: `Config`
+
+```go
+type Config struct {
+ Server struct {
+ Port int
+ TLS struct {
+ Enabled bool
+ CertFile string
+ KeyFile string
+ }
+ }
+ Metrics struct {
+ Enabled bool
+ Port int
+ }
+}
+```
+
+### Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `Load` | `func Load(filename string) (*Config, error)` | Reads and parses YAML config file |
+
+---
+
+## Package: `internal/metrics`
+
+**File:** `internal/metrics/metrics.go`
+
+**Responsibility:** Prometheus metrics registration and exposure.
+
+### Metrics Defined
+
+| Variable | Prometheus Type | Metric Name | Description |
+|----------|----------------|-------------|-------------|
+| `ConnectedClients` | Gauge | `websocket_connected_clients` | Current number of connected clients |
+| `MessagesTotal` | Gauge | `websocket_message` | Total messages processed |
+| `ConnectionsTotal` | Gauge | `websocket_connection` | Total connections established |
+| `DisconnectionsTotal` | Gauge | `websocket_disconnection` | Total disconnections |
+
+> **Note:** All metrics use `promauto.NewGauge` for auto-registration. The "total" metrics use Gauge instead of Counter, which means they track cumulative counts but will reset on restart.
+
+---
+
+## Test Coverage
+
+| Package | Test File | Tests |
+|---------|-----------|-------|
+| `internal/hub` | `hub_test.go` | `TestNew`, `TestClientCount`, `TestBroadcastChannel` |
+| `internal/config` | `config_test.go` | `TestLoad`, `TestLoadFileNotFound` |
+| `internal/metrics` | — | No dedicated tests |
diff --git a/.agents/summary/data_models.md b/.agents/summary/data_models.md
new file mode 100644
index 0000000..fb87452
--- /dev/null
+++ b/.agents/summary/data_models.md
@@ -0,0 +1,142 @@
+# Data Models
+
+## Configuration Model
+
+```mermaid
+classDiagram
+ class Config {
+ +Server ServerConfig
+ +Metrics MetricsConfig
+ }
+ class ServerConfig {
+ +int Port
+ +TLSConfig TLS
+ }
+ class TLSConfig {
+ +bool Enabled
+ +string CertFile
+ +string KeyFile
+ }
+ class MetricsConfig {
+ +bool Enabled
+ +int Port
+ }
+
+ Config --> ServerConfig
+ Config --> MetricsConfig
+ ServerConfig --> TLSConfig
+```
+
+### Config Struct Definition
+
+```go
+type Config struct {
+ Server struct {
+ Port int `yaml:"port"`
+ TLS struct {
+ Enabled bool `yaml:"enabled"`
+ CertFile string `yaml:"cert_file"`
+ KeyFile string `yaml:"key_file"`
+ } `yaml:"tls"`
+ } `yaml:"server"`
+ Metrics struct {
+ Enabled bool `yaml:"enabled"`
+ Port int `yaml:"port"`
+ } `yaml:"metrics"`
+}
+```
+
+### Default Configuration Values
+
+| Field | Default | Notes |
+|-------|---------|-------|
+| `server.port` | 8443 | Standard alternate HTTPS port |
+| `server.tls.enabled` | false (config.yaml) / true (example) | Toggle TLS |
+| `server.tls.cert_file` | `cert.pem` | Relative to working directory |
+| `server.tls.key_file` | `key.pem` | Relative to working directory |
+| `metrics.enabled` | true | Prometheus metrics |
+| `metrics.port` | 9090 | Standard Prometheus port |
+
+---
+
+## Hub State Model
+
+```mermaid
+classDiagram
+ class Hub {
+ -map~*websocket.Conn, bool~ clients
+ -chan []byte broadcast
+ -chan *websocket.Conn register
+ -chan *websocket.Conn unregister
+ -sync.RWMutex mu
+ +New() Hub
+ +Run()
+ +HandleWebSocket(w, r)
+ +ClientCount() int
+ }
+```
+
+### Channel Types
+
+| Channel | Direction | Payload | Buffer |
+|---------|-----------|---------|--------|
+| `register` | Handler → Hub | `*websocket.Conn` | Unbuffered |
+| `unregister` | Reader → Hub | `*websocket.Conn` | Unbuffered |
+| `broadcast` | Reader → Hub | `[]byte` | Unbuffered |
+
+---
+
+## Message Model
+
+The relay server does **not** impose any message structure. Messages are raw `[]byte` payloads passed through as WebSocket text frames.
+
+```mermaid
+graph LR
+ A[Client A sends bytes] --> B[Hub broadcast channel]
+ B --> C[Written as TextMessage to all clients]
+```
+
+The example HTML client uses an informal format:
+```
+{name}
{message_text}
+```
+
+But this is purely client-side convention — the server is format-agnostic.
+
+---
+
+## Metrics Model
+
+```mermaid
+classDiagram
+ class PrometheusMetrics {
+ +Gauge ConnectedClients
+ +Gauge MessagesTotal
+ +Gauge ConnectionsTotal
+ +Gauge DisconnectionsTotal
+ }
+```
+
+| Metric | Update Trigger |
+|--------|---------------|
+| `ConnectedClients` | Set on register/unregister (absolute count) |
+| `MessagesTotal` | Incremented on each broadcast |
+| `ConnectionsTotal` | Incremented on register |
+| `DisconnectionsTotal` | Incremented on unregister |
+
+---
+
+## Connection State Machine
+
+```mermaid
+stateDiagram-v2
+ [*] --> Connecting: HTTP request to /
+ Connecting --> Connected: WebSocket upgrade success
+ Connecting --> Failed: Upgrade error
+ Connected --> Active: Registered in Hub
+ Active --> Active: Sending/Receiving messages
+ Active --> Disconnecting: Read error or client close
+ Disconnecting --> Closed: Unregistered from Hub
+ Failed --> [*]
+ Closed --> [*]
+```
diff --git a/.agents/summary/dependencies.md b/.agents/summary/dependencies.md
new file mode 100644
index 0000000..de2a1c0
--- /dev/null
+++ b/.agents/summary/dependencies.md
@@ -0,0 +1,104 @@
+# Dependencies
+
+## Direct Dependencies
+
+| Package | Version | Purpose | Usage Location |
+|---------|---------|---------|---------------|
+| `github.com/gorilla/websocket` | v1.5.1 | WebSocket protocol implementation | `internal/hub/hub.go` |
+| `github.com/prometheus/client_golang` | v1.17.0 | Prometheus metrics client library | `internal/metrics/metrics.go`, `main.go` |
+| `gopkg.in/yaml.v3` | v3.0.1 | YAML configuration parsing | `internal/config/config.go` |
+
+## Dependency Graph
+
+```mermaid
+graph TD
+ subgraph "Application"
+ MAIN[main.go]
+ HUB[internal/hub]
+ CFG[internal/config]
+ MET[internal/metrics]
+ end
+
+ subgraph "Direct Dependencies"
+ GWS[gorilla/websocket v1.5.1]
+ PROM[prometheus/client_golang v1.17.0]
+ YAML[gopkg.in/yaml.v3 v3.0.1]
+ end
+
+ subgraph "Transitive Dependencies"
+ NET[golang.org/x/net v0.17.0]
+ PROTO[google.golang.org/protobuf v1.31.0]
+ PROMMOD[prometheus/client_model v0.4.1]
+ PROMCOM[prometheus/common v0.44.0]
+ PROMPROC[prometheus/procfs v0.11.1]
+ end
+
+ HUB --> GWS
+ HUB --> MET
+ MET --> PROM
+ CFG --> YAML
+ MAIN --> PROM
+ GWS --> NET
+ PROM --> PROTO
+ PROM --> PROMMOD
+ PROM --> PROMCOM
+ PROM --> PROMPROC
+```
+
+## Indirect (Transitive) Dependencies
+
+| Package | Version | Required By |
+|---------|---------|-------------|
+| `github.com/beorn7/perks` | v1.0.1 | prometheus/client_golang |
+| `github.com/cespare/xxhash/v2` | v2.2.0 | prometheus/client_golang |
+| `github.com/golang/protobuf` | v1.5.3 | prometheus/client_golang |
+| `github.com/kr/text` | v0.2.0 | prometheus (testing) |
+| `github.com/matttproud/golang_protobuf_extensions` | v1.0.4 | prometheus/client_golang |
+| `github.com/prometheus/client_model` | v0.4.1 | prometheus/client_golang |
+| `github.com/prometheus/common` | v0.44.0 | prometheus/client_golang |
+| `github.com/prometheus/procfs` | v0.11.1 | prometheus/client_golang |
+| `golang.org/x/net` | v0.17.0 | gorilla/websocket |
+| `golang.org/x/sys` | v0.13.0 | prometheus/procfs |
+| `google.golang.org/protobuf` | v1.31.0 | prometheus/client_golang |
+
+## Dependency Usage Details
+
+### gorilla/websocket
+
+- **Used for:** WebSocket protocol handling (upgrade, read, write)
+- **Key APIs used:**
+ - `websocket.Upgrader` — HTTP to WebSocket upgrade
+ - `websocket.Conn.ReadMessage()` — Read frames from client
+ - `websocket.Conn.WriteMessage()` — Write frames to client
+ - `websocket.TextMessage` — Message type constant
+
+### prometheus/client_golang
+
+- **Used for:** Application observability metrics
+- **Key APIs used:**
+ - `promauto.NewGauge()` — Auto-registering gauge metrics
+ - `prometheus.GaugeOpts` — Metric configuration
+ - `promhttp.Handler()` — HTTP handler for `/metrics` endpoint
+
+### gopkg.in/yaml.v3
+
+- **Used for:** Configuration file parsing
+- **Key APIs used:**
+ - `yaml.Unmarshal()` — Deserialize YAML into Go structs
+
+## Build & CI Dependencies
+
+| Tool | Purpose | Used In |
+|------|---------|---------|
+| Go 1.21 | Compiler and runtime | CI, Release |
+| golangci-lint | Static analysis / linting | CI |
+| make | Build automation | Local dev, CI |
+| Gitea Actions | CI/CD pipeline runner | `.gitea/workflows/` |
+
+## Security Considerations
+
+| Dependency | Known Issues | Notes |
+|-----------|--------------|-------|
+| `golang.org/x/net` | v0.17.0 | Check for CVEs periodically |
+| `gorilla/websocket` | Archived repository | Consider migration to `nhooyr.io/websocket` or `coder/websocket` long-term |
+| TLS certificates | Local dev certs in repo | Not for production use |
diff --git a/.agents/summary/index.md b/.agents/summary/index.md
new file mode 100644
index 0000000..b161385
--- /dev/null
+++ b/.agents/summary/index.md
@@ -0,0 +1,112 @@
+# Documentation Index — WebSocket Relay
+
+> **Purpose:** This file serves as the primary knowledge base entry point for AI assistants working with this codebase. Read this file first to understand where to find detailed information.
+
+## How to Use This Documentation
+
+1. **Start here** — this index contains summaries of every documentation file
+2. **Check the summary tables** below to determine which file has the information you need
+3. **Only load additional files** when you need deeper detail on a specific topic
+4. **Cross-references** are provided to help navigate between related topics
+
+---
+
+## Project Quick Reference
+
+| Fact | Value |
+|------|-------|
+| **Project** | WebSocket Relay Server |
+| **Language** | Go 1.21 |
+| **Purpose** | Broadcast WebSocket messages to all connected clients (P2P relay) |
+| **Entry Point** | `main.go` |
+| **Config** | `config.yaml` (YAML) |
+| **WebSocket Port** | 8443 (configurable) |
+| **Metrics Port** | 9090 (configurable, optional) |
+| **Build** | `make build` / `make release` |
+| **Test** | `make test` / `go test ./...` |
+| **Architecture** | Hub-and-Spoke broadcast pattern using Go channels |
+
+---
+
+## Documentation Files
+
+### 📋 codebase_info.md
+**What it contains:** Project metadata, directory tree, technology stack, build targets, and codebase statistics.
+**When to consult:** When you need to understand the project layout, find a file, or check what tools/languages are used.
+**Key topics:** Directory structure, Go module info, Makefile targets, dependency counts.
+
+---
+
+### 🏗️ architecture.md
+**What it contains:** System design, Hub-and-Spoke pattern explanation, concurrency model, goroutine lifecycle, security model, and deployment architecture.
+**When to consult:** When you need to understand HOW the system works at a high level, the threading model, or how components interact.
+**Key topics:** CSP concurrency, fan-out broadcasting, TLS configuration, CI/CD pipeline structure.
+**Cross-references:** → `components.md` for implementation details, → `workflows.md` for sequence flows.
+
+---
+
+### 🧩 components.md
+**What it contains:** Detailed description of each Go package — structs, methods, fields, and their responsibilities.
+**When to consult:** When you need to modify a specific package, understand a struct's fields, or find where functionality lives.
+**Key topics:** Hub struct and methods, Config struct, metrics variables, test coverage map.
+**Cross-references:** → `architecture.md` for design rationale, → `interfaces.md` for external contracts.
+
+---
+
+### 🔌 interfaces.md
+**What it contains:** All external interfaces — WebSocket endpoint behavior, metrics endpoint, CLI flags, configuration schema, and integration points.
+**When to consult:** When you need to understand how clients interact with the server, what the API contract is, or how to configure the service.
+**Key topics:** WebSocket message protocol, Prometheus metric names, CLI usage, YAML config schema.
+**Cross-references:** → `data_models.md` for config struct details, → `components.md` for handler implementation.
+
+---
+
+### 📊 data_models.md
+**What it contains:** All data structures — Config struct, Hub state, message format, metrics model, and connection state machine.
+**When to consult:** When you need to understand data shapes, struct definitions, channel types, or state transitions.
+**Key topics:** Config YAML mapping, Hub channels and their payloads, connection lifecycle states.
+**Cross-references:** → `interfaces.md` for how models are exposed externally, → `components.md` for methods operating on these models.
+
+---
+
+### 🔄 workflows.md
+**What it contains:** Step-by-step process flows — startup, connection, broadcast, build/release, development, and error handling.
+**When to consult:** When you need to understand the sequence of operations, debug a flow, or add a new feature that hooks into an existing workflow.
+**Key topics:** Application startup sequence, client lifecycle, CI/CD pipeline steps, error handling paths.
+**Cross-references:** → `architecture.md` for the concurrency model underlying workflows, → `components.md` for method details.
+
+---
+
+### 📦 dependencies.md
+**What it contains:** Complete dependency inventory — direct and transitive deps, their versions, usage locations, security considerations, and build tools.
+**When to consult:** When updating dependencies, evaluating security, understanding what libraries provide, or considering alternatives.
+**Key topics:** gorilla/websocket APIs used, Prometheus client usage, gopkg.in/yaml.v3 usage, transitive dependency tree.
+**Cross-references:** → `components.md` for where dependencies are imported.
+
+---
+
+### 📝 review_notes.md
+**What it contains:** Documentation quality assessment — consistency issues, completeness gaps, bugs found during analysis, and prioritized recommendations.
+**When to consult:** When looking for known issues, potential bugs, or areas needing improvement.
+**Key topics:** Metrics type bug, port mismatch in example, missing features (graceful shutdown, rate limiting), test coverage gaps.
+**Cross-references:** All other files (identifies issues across the entire codebase).
+
+---
+
+## Quick Lookup: Common Questions
+
+| Question | File to Consult |
+|----------|----------------|
+| "What does this project do?" | This file (index.md) |
+| "Where is X implemented?" | `codebase_info.md` → directory tree |
+| "How does the Hub work?" | `components.md` → Hub section |
+| "What's the WebSocket message format?" | `interfaces.md` → WebSocket Endpoint |
+| "How do I build/test?" | `codebase_info.md` → Build Targets |
+| "What metrics are exposed?" | `interfaces.md` → Metrics Endpoint |
+| "What are the config options?" | `interfaces.md` → Configuration Interface |
+| "Are there known bugs?" | `review_notes.md` → Inconsistencies |
+| "What should I improve?" | `review_notes.md` → Recommendations |
+| "What dependencies does it use?" | `dependencies.md` |
+| "How does startup work?" | `workflows.md` → Application Startup |
+| "How are connections handled?" | `workflows.md` → Client Connection Workflow |
+| "What's the threading model?" | `architecture.md` → Concurrency Model |
diff --git a/.agents/summary/interfaces.md b/.agents/summary/interfaces.md
new file mode 100644
index 0000000..51b0a66
--- /dev/null
+++ b/.agents/summary/interfaces.md
@@ -0,0 +1,147 @@
+# Interfaces
+
+## HTTP Endpoints
+
+### WebSocket Endpoint
+
+| Property | Value |
+|----------|-------|
+| **Path** | `/` |
+| **Protocol** | WebSocket (ws:// or wss://) |
+| **Port** | Configurable (default: 8443) |
+| **Handler** | `hub.HandleWebSocket` |
+
+**Upgrade Headers:**
+- Standard WebSocket upgrade
+- `CheckOrigin` accepts all origins
+
+**Message Protocol:**
+- Type: `TextMessage` (opcode 1)
+- Format: Raw bytes (no structured format imposed)
+- Direction: Bidirectional — any message sent is broadcast to all connected clients
+
+```mermaid
+sequenceDiagram
+ participant A as Client A
+ participant S as Relay Server
+ participant B as Client B
+ participant C as Client C
+
+ A->>S: Connect (ws upgrade)
+ B->>S: Connect (ws upgrade)
+ C->>S: Connect (ws upgrade)
+ A->>S: Send "Hello"
+ S->>A: Relay "Hello"
+ S->>B: Relay "Hello"
+ S->>C: Relay "Hello"
+```
+
+> **Note:** The sender also receives their own message back (no sender filtering).
+
+---
+
+### Metrics Endpoint
+
+| Property | Value |
+|----------|-------|
+| **Path** | `/metrics` |
+| **Protocol** | HTTP |
+| **Port** | Configurable (default: 9090) |
+| **Format** | Prometheus text exposition format |
+| **Condition** | Only available when `metrics.enabled: true` |
+
+**Available Metrics:**
+
+```
+# HELP websocket_connected_clients Number of currently connected WebSocket clients
+# TYPE websocket_connected_clients gauge
+websocket_connected_clients 0
+
+# HELP websocket_message Number of WebSocket messages processed
+# TYPE websocket_message gauge
+websocket_message 0
+
+# HELP websocket_connection Number of WebSocket connections established
+# TYPE websocket_connection gauge
+websocket_connection 0
+
+# HELP websocket_disconnection Number of WebSocket disconnections
+# TYPE websocket_disconnection gauge
+websocket_disconnection 0
+```
+
+---
+
+## CLI Interface
+
+```
+Usage: websocket-relay [flags]
+
+Flags:
+ --config-file string Path to configuration file (default "config.yaml")
+```
+
+---
+
+## Configuration Interface (YAML)
+
+```yaml
+server:
+ port: 8443 # Server listen port
+ tls:
+ enabled: true # Enable TLS (wss://)
+ cert_file: cert.pem # Path to TLS certificate
+ key_file: key.pem # Path to TLS private key
+
+metrics:
+ enabled: true # Enable Prometheus metrics endpoint
+ port: 9090 # Metrics server port
+```
+
+---
+
+## Internal Go Interfaces (Implicit)
+
+The codebase doesn't define explicit Go interfaces but uses the following implicit contracts:
+
+### Hub Contract
+
+```go
+// Hub manages WebSocket connections and broadcasts messages
+type Hub interface {
+ Run() // Start event loop
+ HandleWebSocket(http.ResponseWriter, *http.Request) // HTTP handler
+ ClientCount() int // Connected client count
+}
+```
+
+### Config Contract
+
+```go
+// Config loading
+type ConfigLoader interface {
+ Load(filename string) (*Config, error)
+}
+```
+
+---
+
+## Integration Points
+
+```mermaid
+graph LR
+ subgraph External
+ PROM[Prometheus]
+ BROWSERS[Browser Clients]
+ APPS[Application Clients]
+ end
+
+ subgraph "WebSocket Relay"
+ WS[WebSocket :8443]
+ MET[Metrics :9090]
+ end
+
+ BROWSERS -->|ws/wss| WS
+ APPS -->|ws/wss| WS
+ PROM -->|HTTP GET /metrics| MET
+```
diff --git a/.agents/summary/review_notes.md b/.agents/summary/review_notes.md
new file mode 100644
index 0000000..0ee7505
--- /dev/null
+++ b/.agents/summary/review_notes.md
@@ -0,0 +1,75 @@
+# Documentation Review Notes
+
+## Consistency Check Results
+
+### ✅ Consistent
+
+- Configuration documented in `data_models.md` matches actual `config.go` struct
+- Hub methods documented in `components.md` match actual implementation
+- CLI flags documented in `interfaces.md` match `main.go`
+- Build commands in `codebase_info.md` match `Makefile`
+
+### ⚠️ Inconsistencies Found
+
+| Issue | Location | Details |
+|-------|----------|---------|
+| **Port mismatch in example client** | `example/index.html` | Uses `ws://localhost:8000/` but config default is port `8443` |
+| **Metrics type mismatch** | `internal/metrics/metrics.go` | `MessagesTotal`, `ConnectionsTotal`, `DisconnectionsTotal` are defined as `Gauge` but semantically represent counters (monotonically increasing values). Should be `Counter` type. |
+| **Silent client removal** | `internal/hub/hub.go` | During broadcast, write errors cause client removal without going through the `unregister` channel, meaning `DisconnectionsTotal` and `ConnectedClients` metrics won't be updated correctly. |
+| **README port reference** | `README.md` | Mentions TLS is enabled by default, but `config.yaml` has `tls.enabled: false` |
+
+---
+
+## Completeness Check Results
+
+### ✅ Well-Documented Areas
+
+- Core WebSocket relay logic
+- Configuration structure and loading
+- Build and deployment pipeline
+- Metrics definitions
+
+### ❌ Documentation Gaps
+
+| Gap | Severity | Recommendation |
+|-----|----------|----------------|
+| **No graceful shutdown** | Medium | Document that the server lacks graceful shutdown — connections are terminated abruptly on SIGTERM |
+| **No rate limiting** | Medium | Document absence of rate limiting and implications for production use |
+| **No message size limits** | Medium | No `ReadLimit` set on WebSocket connections — potential DoS vector |
+| **No health check endpoint** | Low | No `/health` or `/ready` endpoint for orchestrators |
+| **No connection limits** | Medium | No max client count — server could be overwhelmed |
+| **No logging configuration** | Low | Uses default `log` package with no level control |
+| **No deployment docs** | Medium | No systemd unit file, Docker instructions, or k8s manifests |
+| **Missing test coverage** | Medium | `internal/metrics` has no tests; hub integration tests (actual WebSocket connections) missing |
+
+---
+
+## Language Support
+
+| Aspect | Support Level | Notes |
+|--------|--------------|-------|
+| Go source analysis | ✅ Full | All Go code fully analyzed |
+| HTML/JS (example) | ✅ Full | Simple single-file client analyzed |
+| YAML configs | ✅ Full | Configuration fully documented |
+| Makefile | ✅ Full | All targets documented |
+| Gitea Actions YAML | ✅ Full | CI/CD pipelines documented |
+
+---
+
+## Recommendations
+
+### High Priority
+1. **Fix metrics types**: Change `MessagesTotal`, `ConnectionsTotal`, `DisconnectionsTotal` from `Gauge` to `Counter`
+2. **Fix broadcast disconnect handling**: Route write-error disconnections through the unregister channel to maintain accurate metrics
+3. **Add message size limits**: Set `conn.SetReadLimit()` to prevent memory exhaustion
+
+### Medium Priority
+4. **Add graceful shutdown**: Use `context.Context` and `http.Server.Shutdown()`
+5. **Add health endpoint**: Simple `/health` returning 200 OK
+6. **Add integration tests**: Test actual WebSocket connections end-to-end
+7. **Fix example port**: Update `example/index.html` to use port 8443
+
+### Low Priority
+8. **Add structured logging**: Replace `log` with `slog` or `zerolog`
+9. **Add connection limits**: Max concurrent connections configuration
+10. **Add Docker support**: Dockerfile and docker-compose for easy deployment
diff --git a/.agents/summary/workflows.md b/.agents/summary/workflows.md
new file mode 100644
index 0000000..c4e347b
--- /dev/null
+++ b/.agents/summary/workflows.md
@@ -0,0 +1,140 @@
+# Workflows
+
+## Application Startup
+
+```mermaid
+flowchart TD
+ START[Application Start] --> PARSE[Parse CLI flags]
+ PARSE --> LOAD[Load config.yaml]
+ LOAD -->|Error| FATAL[log.Fatal - exit]
+ LOAD -->|Success| CREATE[Create Hub]
+ CREATE --> RUN[Start Hub.Run goroutine]
+ RUN --> METRICS{Metrics enabled?}
+ METRICS -->|Yes| METSRV[Start metrics server goroutine on :9090]
+ METRICS -->|No| SKIP[Skip metrics]
+ METSRV --> TLS{TLS enabled?}
+ SKIP --> TLS
+ TLS -->|Yes| TLSSERVE[ListenAndServeTLS on :8443]
+ TLS -->|No| HTTPSERVE[ListenAndServe on :8443]
+```
+
+---
+
+## Client Connection Workflow
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant HTTP as HTTP Server
+ participant Upgrader as WebSocket Upgrader
+ participant Hub as Hub.Run()
+ participant Metrics
+
+ Client->>HTTP: GET / (Upgrade: websocket)
+ HTTP->>Upgrader: CheckOrigin (always true)
+ Upgrader->>HTTP: Upgrade response
+ HTTP->>Hub: register <- conn
+ Hub->>Metrics: ConnectedClients.Set(n)
+ Hub->>Metrics: ConnectionsTotal.Inc()
+ Note over HTTP: Spawn reader goroutine
+
+ loop Message Loop
+ Client->>HTTP: WebSocket frame
+ HTTP->>Hub: broadcast <- message
+ Hub->>Metrics: MessagesTotal.Inc()
+ Hub->>Client: WriteMessage to all clients
+ end
+
+ Note over HTTP: Read error or client disconnect
+ HTTP->>Hub: unregister <- conn
+ Hub->>Metrics: ConnectedClients.Set(n-1)
+ Hub->>Metrics: DisconnectionsTotal.Inc()
+ Hub->>Hub: Close connection, remove from map
+```
+
+---
+
+## Build and Release Workflow
+
+```mermaid
+flowchart LR
+ subgraph Development
+ CODE[Write Code] --> PUSH[Push to main/develop]
+ end
+
+ subgraph "CI Pipeline"
+ PUSH --> TEST[go test -v ./...]
+ PUSH --> LINT[golangci-lint]
+ TEST --> BUILD[make build]
+ end
+
+ subgraph "Release Pipeline"
+ TAG[Push v* tag] --> REL_BUILD[Cross-compile]
+ REL_BUILD --> LINUX[linux/amd64 binary]
+ REL_BUILD --> MACOS[darwin/arm64 binary]
+ LINUX --> RELEASE[Gitea Release]
+ MACOS --> RELEASE
+ end
+```
+
+---
+
+## Development Workflow
+
+```mermaid
+flowchart TD
+ START[Clone repo] --> DEPS[make deps / go mod tidy]
+ DEPS --> CONFIG[Edit config.yaml]
+ CONFIG --> RUN[make run]
+ RUN --> TEST_LOCAL[Test with example/index.html]
+ TEST_LOCAL --> WRITE[Write code changes]
+ WRITE --> UNIT[make test]
+ UNIT -->|Pass| COMMIT[git commit]
+ UNIT -->|Fail| WRITE
+ COMMIT --> PUSH[git push]
+ PUSH --> CI[CI runs tests + lint]
+```
+
+---
+
+## Message Broadcast Workflow
+
+```mermaid
+flowchart TD
+ MSG[Client sends message] --> CHAN[broadcast channel receives []byte]
+ CHAN --> INC[MessagesTotal.Inc]
+ INC --> LOCK[RLock clients map]
+ LOCK --> ITER{For each client}
+ ITER -->|Next client| WRITE[WriteMessage]
+ WRITE -->|Success| ITER
+ WRITE -->|Error| REMOVE[Remove client, close conn]
+ REMOVE --> ITER
+ ITER -->|Done| UNLOCK[RUnlock]
+```
+
+---
+
+## Error Handling Workflows
+
+### Connection Upgrade Failure
+
+```mermaid
+flowchart LR
+ REQ[HTTP Request] --> UPG{Upgrade succeeds?}
+ UPG -->|No| LOG[Log error]
+ LOG --> RETURN[Return - no cleanup needed]
+ UPG -->|Yes| REGISTER[Continue with registration]
+```
+
+### Write Error During Broadcast
+
+```mermaid
+flowchart LR
+ WRITE[WriteMessage] --> ERR{Error?}
+ ERR -->|No| NEXT[Continue to next client]
+ ERR -->|Yes| DEL[Delete from clients map]
+ DEL --> CLOSE[Close connection]
+ CLOSE --> NEXT
+```
+
+> **Note:** Write errors during broadcast silently remove the failing client without triggering the unregister channel. This is a potential inconsistency — the `DisconnectionsTotal` metric won't be incremented and `ConnectedClients` gauge won't be updated for these removals.
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..1490be4
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,202 @@
+# AGENTS.md — AI Assistant Guide for websocket-relay
+
+> This file provides context for AI coding assistants working on this project. It focuses on information not found in README.md and is optimized for quick comprehension.
+
+## Project Identity
+
+**websocket-relay** is a minimal Go WebSocket relay server that broadcasts every incoming message to all connected clients (hub-and-spoke / fan-out pattern). It supports TLS, Prometheus metrics, and graceful shutdown.
+
+---
+
+## Directory Structure
+
+```
+websocket-relay/
+├── main.go # Entry point: config loading, signal handling, graceful shutdown
+├── internal/
+│ ├── config/config.go # YAML config loader (server port, TLS, metrics)
+│ ├── hub/hub.go # Core logic: WebSocket hub, connection mgmt, broadcast
+│ └── metrics/metrics.go # Prometheus counter/gauge definitions
+├── example/index.html # Browser P2P chat demo client
+├── config.yaml # Runtime config (edit for local dev)
+├── config.example.yaml # Reference config with TLS enabled
+├── Makefile # build, test, release, run, deps, clean
+├── .gitea/workflows/
+│ ├── ci.yml # Push/PR → test + lint
+│ └── release.yml # Tag v* → cross-compile + Gitea release
+└── .agents/summary/ # Generated documentation (see index.md)
+```
+
+---
+
+## Architecture at a Glance
+
+```mermaid
+graph LR
+ C1[Client] -->|ws| HUB[Hub goroutine]
+ C2[Client] -->|ws| HUB
+ HUB -->|broadcast| C1
+ HUB -->|broadcast| C2
+ HUB --> MET[Prometheus :9090]
+```
+
+- **Single Hub goroutine** runs a `select` loop on 4 channels: `register`, `unregister`, `broadcast`, `stop`
+- **Per-client reader goroutine** reads messages and pushes to `broadcast` channel
+- **No write goroutine** — writes happen inline during broadcast (under RLock)
+- **Thread safety** via `sync.RWMutex` on the clients map
+- **Graceful shutdown** via SIGINT/SIGTERM → HTTP server shutdown → Hub shutdown → clean exit
+
+---
+
+## Coding Patterns
+
+### Package Layout
+- All internal packages live under `internal/` (Go internal package convention — cannot be imported externally)
+- Flat package structure — each package has one primary `.go` file
+- Tests use `_test.go` suffix in the same package (white-box testing)
+
+### Naming Conventions
+- Exported functions/types: `PascalCase` (e.g., `New`, `Run`, `HandleWebSocket`, `Shutdown`)
+- Config struct uses nested anonymous structs with `yaml` tags
+- Metrics use package-level `var` block with `promauto` for auto-registration
+
+### Error Handling
+- Fatal errors at startup → `log.Fatal()`
+- WebSocket upgrade errors → logged and returned (no panic)
+- Write errors during broadcast → client removed with proper metrics update
+- Config load errors → fatal (server won't start without valid config)
+- Shutdown errors → logged but not fatal (best-effort cleanup)
+
+### Concurrency Pattern
+- CSP via channels (not mutexes for coordination)
+- The Hub `select` loop is the single coordination point
+- RWMutex used additionally for broadcast iteration safety
+- `stop` channel (closed on shutdown) signals the Hub to terminate
+
+### Graceful Shutdown Pattern
+- `main()` listens for SIGINT/SIGTERM via `os/signal`
+- On signal: stops accepting new HTTP connections → shuts down Hub → exits
+- 10-second timeout ensures the process doesn't hang indefinitely
+- Clients receive a WebSocket Close frame (`CloseGoingAway`) before disconnection
+
+---
+
+## How to Write & Run Tests
+
+### Running Tests
+```bash
+make test # Runs: go test -v ./...
+go test ./internal/hub/ # Single package
+go test -run TestNew ./internal/hub/ # Single test
+```
+
+### Test Conventions
+- Test files: `*_test.go` in same package
+- Use standard `testing.T` — no test framework
+- Table-driven tests not yet adopted (tests are simple)
+- Temp files for config tests (`os.CreateTemp`)
+- Hub tests start `go h.Run()` and use `defer h.Shutdown()` for cleanup
+- Integration tests use `httptest.Server` + real WebSocket dials
+
+### Adding New Tests
+```go
+// File: internal//_test.go
+package
+
+import "testing"
+
+func TestFeature(t *testing.T) {
+ // Setup
+ h := New()
+ go h.Run()
+ defer h.Shutdown()
+
+ // Assert
+ if h.ClientCount() != 0 {
+ t.Errorf("expected 0, got %d", h.ClientCount())
+ }
+}
+```
+
+### Missing Test Coverage
+- `internal/metrics` — no tests (metrics are auto-registered, mostly testing Prometheus library)
+- No benchmarks
+
+---
+
+## Configuration
+
+Config is loaded from YAML (default: `config.yaml`, override with `--config-file` flag):
+
+```yaml
+server:
+ port: 8443
+ tls:
+ enabled: false # Set true + provide cert/key for wss://
+ cert_file: cert.pem
+ key_file: key.pem
+metrics:
+ enabled: true
+ port: 9090
+```
+
+---
+
+## Build & Deployment
+
+```bash
+make build # → build/websocket-relay
+make release # → build/websocket-relay-linux-amd64, build/websocket-relay-darwin-arm64
+make run # → go run .
+make deps # → go mod tidy
+make clean # → rm build artifacts
+```
+
+### Release Process
+1. Tag with `v*` prefix (e.g., `git tag v1.2.0`)
+2. Push tag → Gitea Actions builds linux/amd64 + darwin/arm64
+3. Binaries uploaded as Gitea Release assets
+
+---
+
+## Known Issues & Technical Debt
+
+| Issue | Severity | Location |
+|-------|----------|----------|
+| No message size limits (`ReadLimit`) | Security | `internal/hub/hub.go` |
+| No connection count limits | Security | `internal/hub/hub.go` |
+| `gorilla/websocket` is archived | Debt | `go.mod` |
+
+---
+
+## Adding Features — Quick Guide
+
+### Adding a new config field
+1. Add field to `Config` struct in `internal/config/config.go` with `yaml` tag
+2. Add to `config.yaml` and `config.example.yaml`
+3. Use in `main.go` via `cfg.YourSection.YourField`
+
+### Adding a new metric
+1. Add `var` to `internal/metrics/metrics.go` using `promauto.NewGauge/Counter/Histogram`
+2. Call `metrics.YourMetric.Inc()` (or `.Set()`, `.Observe()`) where needed
+
+### Adding a new HTTP endpoint
+1. Add handler method to Hub or create new handler
+2. Register in `main.go`: `mux.HandleFunc("/path", handler)`
+
+### Adding a new internal package
+1. Create `internal//.go`
+2. Import as `websocket-relay/internal/`
+
+---
+
+## Detailed Documentation
+
+For deeper analysis, see `.agents/summary/index.md` which provides a complete knowledge base with:
+- Architecture diagrams and concurrency model
+- Component-level documentation with all structs/methods
+- Complete interface specifications
+- Data model definitions and state machines
+- Workflow sequence diagrams
+- Dependency analysis and security notes
+- Prioritized improvement recommendations
diff --git a/README.md b/README.md
index ca70391..288ee85 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,122 @@
# WebSocket Relay Server
-A minimal Go WebSocket relay server with SSL support for P2P connections.
+A minimal Go WebSocket relay server that broadcasts every incoming message to all connected clients. Supports TLS, Prometheus metrics, and graceful shutdown.
-## Setup
+## Features
+
+- **Fan-out broadcasting** — every message is relayed to all connected clients
+- **TLS support** — optional `wss://` via cert/key PEM files
+- **Prometheus metrics** — connection counts, message totals, disconnections
+- **Graceful shutdown** — clean exit on SIGINT/SIGTERM with client notification
+- **Zero dependencies at runtime** — single static binary
+
+## Quick Start
```bash
+# Install dependencies
go mod tidy
-# Configure via config.yaml (see config.yaml for options)
-go run main.go --config-file=./config.yaml
+
+# Run the server (defaults to ws://localhost:8443)
+make run
+
+# Or with a custom config
+go run . --config-file=./config.yaml
```
+Open `example/index.html` in multiple browser tabs to test the P2P chat demo.
+
## Configuration
-Edit `config.yaml` to configure:
-- **Server port and TLS settings**
-- **SSL certificate paths**
+Edit `config.yaml`:
+
+```yaml
+server:
+ port: 8443
+ tls:
+ enabled: false # Set true for wss://
+ cert_file: cert.pem
+ key_file: key.pem
+
+metrics:
+ enabled: true
+ port: 9090 # Prometheus metrics at :9090/metrics
+```
+
+Override the config file path with `--config-file`:
+
+```bash
+./websocket-relay --config-file=/etc/relay/config.yaml
+```
## Usage
-- WebSocket endpoint: `/`
-- All WebSocket messages are relayed to all connected clients
+Connect any WebSocket client to the server:
+
+```javascript
+const ws = new WebSocket('ws://localhost:8443/');
+
+ws.onmessage = (event) => console.log('Received:', event.data);
+ws.onopen = () => ws.send('Hello from client!');
+```
+
+With TLS enabled:
+
+```javascript
+const ws = new WebSocket('wss://localhost:8443/');
+```
+
+All messages sent by any client are broadcast to every connected client (including the sender).
+
+## Build
+
+```bash
+make build # Build binary → build/websocket-relay
+make release # Cross-compile linux/amd64 + darwin/arm64
+make clean # Remove build artifacts
+```
## Testing
-```javascript
-// For TLS enabled (default config)
-const ws = new WebSocket('wss://localhost:8443/');
-// For HTTP only
-// const ws = new WebSocket('ws://localhost:8443/');
-ws.onmessage = (event) => console.log('Received:', event.data);
-ws.send('Hello from client!');
-```
\ No newline at end of file
+```bash
+make test # Run all tests (unit + integration)
+```
+
+## Metrics
+
+When `metrics.enabled` is `true`, Prometheus metrics are exposed at `http://localhost:9090/metrics`:
+
+| Metric | Type | Description |
+|--------|------|-------------|
+| `websocket_connected_clients` | Gauge | Currently connected clients |
+| `websocket_messages_total` | Counter | Total messages relayed |
+| `websocket_connections_total` | Counter | Total connections established |
+| `websocket_disconnections_total` | Counter | Total disconnections |
+
+## Graceful Shutdown
+
+The server handles `SIGINT` and `SIGTERM` signals:
+
+1. Stops accepting new connections
+2. Sends WebSocket `CloseGoingAway` frame to all connected clients
+3. Closes all connections and exits cleanly
+
+Shutdown timeout is 10 seconds.
+
+## Project Structure
+
+```
+websocket-relay/
+├── main.go # Entry point, signal handling, graceful shutdown
+├── internal/
+│ ├── config/config.go # YAML config loader
+│ ├── hub/hub.go # WebSocket hub, connection management, broadcast
+│ └── metrics/metrics.go # Prometheus metric definitions
+├── example/index.html # Browser P2P chat demo
+├── config.yaml # Runtime configuration
+├── config.example.yaml # Example config with TLS enabled
+└── Makefile # Build, test, release commands
+```
+
+## License
+
+See repository for license details.
diff --git a/example/index.html b/example/index.html
index f2c23b1..c519a7f 100644
--- a/example/index.html
+++ b/example/index.html
@@ -49,7 +49,7 @@
let ws;
function connect() {
- ws = new WebSocket('ws://localhost:8000/');
+ ws = new WebSocket('ws://localhost:8443/');
ws.onmessage = (event) => {
console.log('Received:', event.data);
@@ -83,4 +83,4 @@