6 Commits

Author SHA1 Message Date
clawbot
aa746177e5 fix: decouple lint stage from web-builder in Dockerfile
All checks were successful
check / check (push) Successful in 5s
Remove COPY --from=web-builder from the lint stage so it can run
independently and fail fast. Create placeholder files for the
go:embed directive instead. The build stage still uses the real
SPA assets from web-builder.

Update README to reflect that lint no longer depends on web-builder.
2026-03-10 02:45:27 -07:00
user
91da9eb8c7 docs: update README project structure and Dockerfile description
All checks were successful
check / check (push) Successful in 4s
- Remove web/dist/ individual files from project structure (now generated
  at Docker build time, not committed)
- Add web/src/, build.sh, package.json, package-lock.json to structure
- Update Dockerfile description from two-stage to four-stage build
  (web-builder, lint, builder, final)
- Remove outdated example Dockerfile snippet
2026-03-09 17:35:19 -07:00
user
4b2888cb90 fix: remove build artifacts from repo, build SPA in Docker
All checks were successful
check / check (push) Successful in 4s
- Remove web/dist/ from git tracking (build output)
- Add web/dist/ to .gitignore
- Add Node.js web-builder stage to Dockerfile to compile SPA at build time
- Update REPO_POLICIES.md from upstream sneak/prompts (build artifacts policy)
2026-03-09 17:25:49 -07:00
78d657111b Rename replay → initChannelState
All checks were successful
check / check (push) Successful in 2m20s
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.
2026-03-09 17:00:56 -07:00
user
096fb2b207 docs: document ?replay=1 query parameter for GET /state 2026-03-09 17:00:56 -07:00
user
737686006e fix: replay channel state on SPA reconnect
When a client reconnects to an existing session (e.g. browser tab
closed and reopened), the server now enqueues synthetic JOIN messages
plus TOPIC/NAMES numerics for every channel the session belongs to.
These are delivered only to the reconnecting client, not broadcast
to other users.

Server changes:
- Add replayChannelState() to handlers that enqueues per-channel
  JOIN + join-numerics (332/353/366) to a specific client.
- HandleState accepts ?replay=1 query parameter to trigger replay.
- HandleLogin (password auth) also replays channel state for the
  new client since it creates a fresh client for an existing session.

SPA changes:
- On resume, call /state?replay=1 instead of /state so the server
  enqueues channel state into the message queue.
- processMessage now creates channel tabs when receiving a JOIN
  where msg.from matches the current nick (handles both live joins
  and replayed joins on reconnect).
- onLogin no longer re-sends JOIN commands for saved channels on
  resume — the server handles it via the replay mechanism, avoiding
  spurious JOIN broadcasts to other channel members.

Closes #60
2026-03-09 17:00:56 -07:00
3 changed files with 8 additions and 21 deletions

View File

@@ -1374,16 +1374,14 @@ Return server metadata. No authentication required.
```json
{
"name": "My NeoIRC Server",
"version": "0.1.0",
"motd": "Welcome! Be nice.",
"users": 42
}
```
| Field | Type | Description |
|-----------|---------|-------------|
|---------|---------|-------------|
| `name` | string | Server display name |
| `version` | string | Server version |
| `motd` | string | Message of the day |
| `users` | integer | Number of currently active user sessions |

View File

@@ -2393,7 +2393,6 @@ func (hdlr *Handlers) HandleServerInfo() http.HandlerFunc {
hdlr.respondJSON(writer, request, map[string]any{
"name": hdlr.params.Config.ServerName,
"version": hdlr.params.Globals.Version,
"motd": hdlr.params.Config.MOTD,
"users": users,
}, http.StatusOK)

View File

@@ -16,11 +16,6 @@ import (
const routeTimeout = 60 * time.Second
// cspHeader is the Content-Security-Policy applied to the embedded web SPA.
// The SPA loads external scripts and stylesheets from the same origin only;
// all API communication uses same-origin fetch (no WebSockets).
const cspHeader = "default-src 'self'; script-src 'self'; style-src 'self'"
// SetupRoutes configures the HTTP routes and middleware.
func (srv *Server) SetupRoutes() {
srv.router = chi.NewRouter()
@@ -138,11 +133,6 @@ func (srv *Server) setupSPA() {
writer http.ResponseWriter,
request *http.Request,
) {
writer.Header().Set(
"Content-Security-Policy",
cspHeader,
)
readFS, ok := distFS.(fs.ReadFileFS)
if !ok {
fileServer.ServeHTTP(writer, request)