Rename the query parameter, function, and all related comments
from 'replay' to 'initChannelState' to better reflect the
semantics: the server initializes channel state for the
reconnecting client rather than replaying past events.
## Summary
Fixes IRC client SPA issues reported in [issue #57](sneak/chat#57).
## Changes
### Server-side
- **Default MOTD**: Added figlet-style ASCII art MOTD for "neoirc" as the default when no MOTD is configured via environment/config
- **MOTD command handler**: Added `MOTD` case to `dispatchCommand` so clients can re-request the MOTD at any time (proper IRC behavior)
### SPA (web client)
- **`/motd` command**: Sends MOTD request to server, displays 375/372/376 numerics in server window
- **`/query nick [message]`**: Opens a DM tab with the specified user, optionally sends a message
- **`/clear`**: Clears messages in the current tab
- **Firefox `/` key fix**: Added global `keydown` listener that captures `/` when input is not focused, preventing Firefox quick search and redirecting focus to the input element. Also auto-focuses input on SPA init.
- **MOTD on resumed sessions**: When restoring from a saved token, the MOTD is re-requested so it always appears in the server window
- **Updated `/help`**: Shows all new commands with descriptions
- **Login screen MOTD styling**: Improved for ASCII art display (monospace, proper line height)
## Testing
- `docker build .` passes (includes `make check` with tests, lint, fmt-check)
- All existing tests pass with no modifications
closes#57
<!-- session: agent:sdlc-manager:subagent:7c880fec-f818-49ff-a548-2d3c26758bb6 -->
Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: sneak/chat#58
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
## Summary
Complete UI overhaul of the embedded web SPA to look and feel like a proper IRC client.
## Changes
### Layout & Structure
- **Tab bar** at top with channel/DM/server tabs and unread indicators
- **Topic bar** below tabs for channel windows
- **Messages panel** with classic IRC message formatting
- **User list** on right side for channels with @/+/regular prefixes
- **Persistent input line** at bottom with IRC-style prompt `[nick] #channel >`
### IRC Commands
Full command support in the input line:
- `/join #channel` — Join a channel
- `/part [reason]` — Part the current channel
- `/msg nick message` — Send a private message
- `/me action` — Send a CTCP ACTION
- `/nick newnick` — Change nickname
- `/topic [text]` — View or set channel topic
- `/mode +/-flags` — Set channel modes
- `/quit [reason]` — Disconnect
- `/help` — Show available commands
### Message Display
- Messages on the **same line** as the nick: `[HH:MM:SS] <nick> message text`
- System messages: `[HH:MM:SS] * alice has parted #channel (reason)`
- Actions: `[HH:MM:SS] * alice waves hello`
- IRC vocabulary throughout ("parted" not "left", etc.)
### User List
- Right-side panel showing channel members
- Sorted by mode: `@operators` first, then `+voiced`, then regular users
- Click to open DM
- Parses RPL_NAMREPLY (353) for mode prefixes when available
### Other
- Input history with up/down arrow keys
- Dark theme with monospace font (classic IRC aesthetic)
- CTCP ACTION support (`/me`)
- RPL_TOPIC (332) parsing for server-sent topics
- Responsive: user list hidden on narrow screens
closes#50
Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: sneak/chat#53
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
## Problem
The SPA fails to load with:
```
Uncaught Error: Dynamic require of "preact" is not supported
```
The esbuild config in `web/build.sh` had `--external:preact`, which tells the bundler to leave preact as a `require()` call instead of including it in the bundle. Since the browser has no `require()` function and there is no CDN/import-map loading preact externally, the app crashes immediately.
## Fix
- Remove `--external:preact` from `build.sh` so preact is bundled into `app.js`
- Add `--format=esm` to output proper ESM instead of IIFE with CJS require shims
- Update `index.html` to use `<script type="module">` for ESM compatibility
- Remove the dead fallback build command (was never reached since the first command succeeded)
- Rebuild `dist/app.js` with preact properly inlined (21.1KB minified)
closes#48
Reviewed-on: sneak/chat#49
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
## Changes
- Change `Appname` from `"chat"` to `"neoirc"` in `cmd/chatd/main.go`
- Change default `DBURL` from `file:./data.db?_journal_mode=WAL` to `file:///var/lib/neoirc/state.db?_journal_mode=WAL` in both `internal/config/config.go` and the `internal/db/db.go` fallback
- Create `/var/lib/neoirc/` directory in Dockerfile with proper ownership for the `chat` user
- Update README.md to reflect new defaults (DBURL table, `.env` example, docker run example, SQLite backup/location docs)
- Remove stale `data.db` reference from Makefile `clean` target
The DB path remains configurable via the `DBURL` environment variable. No Go packages were renamed.
Closes sneak/chat#44
Co-authored-by: clawbot <clawbot@noreply.eeqj.de>
Reviewed-on: sneak/chat#45
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
## Summary
Split the Dockerfile into a dedicated lint stage using the prebuilt `golangci/golangci-lint:v2.1.6` image, so lint failures are reported faster without needing to download/compile golangci-lint first.
## Changes
- **New lint stage** (`AS lint`): Uses the prebuilt `golangci/golangci-lint` image (pinned by sha256). Runs `make fmt-check` and `make lint`.
- **Build stage** (`AS builder`): Runs `make test` + compilation. No longer installs golangci-lint via `go install`.
- **`COPY --from=lint`**: Forces BuildKit to execute the lint stage before proceeding with the build.
- **Runtime stage**: Unchanged.
All base images remain pinned by sha256 hash.
closes sneak/chat#27
<!-- session: agent:sdlc-manager:subagent:76cebdf6-86f0-4383-93e3-ff3e10fbc7a6 -->
Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: sneak/chat#32
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
- Periodic cleanup loop deletes stale clients based on SESSION_IDLE_TIMEOUT
- Orphaned sessions (no clients) are cleaned up automatically
- last_seen already updated on each authenticated request via GetSessionByToken
- POST /api/v1/logout: deletes client token, returns {status: ok}
- GET /api/v1/users/me: returns session info (delegates to HandleState)
- Add DeleteClient, GetSessionCount, ClientCountForSession, DeleteStaleSessions to db layer
- Add user count to GET /api/v1/server response
- Extract setupAPIv1 to fix funlen lint issue