Severity: Trivial / nice-to-have (explicitly a judgement call — closing as not_planned is a legitimate outcome)
Category: Performance (network)
Affected files: src/server/index.ts, package.json. Line numbers refer to main @ 3ac64da.
Problem
/api/bootstrap returns up to serverMaxSyncLines (≤ 500) full LogLine objects — with rawLine and message both carrying the line text, plus metadata, that is roughly 150–250 KB of JSON per page load. /api/resync in reset mode is the same size, and it fires on every reconnect and on tab-visibility resumes. Nothing is compressed today. Log text compresses extremely well (repetitive timestamps, logger names, duplicated raw/message fields): gzip typically brings this to ~10–15 % of the original size.
On a wired LAN this is irrelevant — which is why this issue is a consider, not a defect. It becomes noticeable for the documented reverse-proxy/remote-access setup and for Wi-Fi dashboards on busy networks.
Decision to make first
The project convention is to keep dependencies minimal. There are three reasonable outcomes; pick one and either implement or close:
- Do nothing / close as not planned — valid if the maintainer considers LAN-only the primary target. If a reverse proxy is already in place for auth, it can also do compression (
gzip on; in nginx / encode gzip in Caddy); a README note recommending that is a zero-dependency alternative.
- Add the
compression middleware (the standard Express solution, small and stable).
- No new dependency: use Node's built-in
node:zlib in a tiny hand-rolled middleware for the two JSON endpoints. More code to own; only worth it if avoiding the dependency matters more than the ~30 lines.
Option 2 is the recommended implementation if compression is wanted; option 1's README note is the recommended minimum.
Suggested implementation (option 2)
npm install compression (+ @types/compression as a dev dependency).
- In
src/server/index.ts, mount it only for the API router and never for the SSE stream — compressing text/event-stream buffers events and breaks live delivery:
import compression from 'compression';
app.use(
'/api',
compression({
filter: (request, response) =>
request.path !== '/stream' && compression.filter(request, response)
}),
apiLimiter,
createApiRouter({ … })
);
Notes for the implementer:
- Inside a router mounted at
/api, request.path is '/stream' for /api/stream — verify with a test rather than assuming (if it turns out to be the full path in this position, match accordingly).
- Keep
express.static uncompressed or include it consciously — the JS bundle (~tens of KB) also benefits, but etag-based 304s already keep repeat loads cheap; compressing only /api is the smaller, safer change.
- Default threshold (1 KB) is fine; small responses like
/api/health stay uncompressed automatically.
- If instead option 1 is chosen: add a short README note in the reverse-proxy section recommending enabling gzip/brotli at the proxy for
/api/bootstrap and /api/resync, and explicitly not for /api/stream.
Acceptance criteria (if implemented as option 2)
GET /api/bootstrap with Accept-Encoding: gzip → response has Content-Encoding: gzip and decodes to the same JSON as before.
GET /api/stream → no Content-Encoding, first SSE event arrives immediately (add/extend a route test asserting the header is absent).
npm test, npm run typecheck, and npm run build pass; README.md documents the behavior briefly.
Severity: Trivial / nice-to-have (explicitly a judgement call — closing as
not_plannedis a legitimate outcome)Category: Performance (network)
Affected files:
src/server/index.ts,package.json. Line numbers refer tomain@3ac64da.Problem
/api/bootstrapreturns up toserverMaxSyncLines(≤ 500) fullLogLineobjects — withrawLineandmessageboth carrying the line text, plus metadata, that is roughly 150–250 KB of JSON per page load./api/resyncin reset mode is the same size, and it fires on every reconnect and on tab-visibility resumes. Nothing is compressed today. Log text compresses extremely well (repetitive timestamps, logger names, duplicated raw/message fields): gzip typically brings this to ~10–15 % of the original size.On a wired LAN this is irrelevant — which is why this issue is a consider, not a defect. It becomes noticeable for the documented reverse-proxy/remote-access setup and for Wi-Fi dashboards on busy networks.
Decision to make first
The project convention is to keep dependencies minimal. There are three reasonable outcomes; pick one and either implement or close:
gzip on;in nginx /encode gzipin Caddy); a README note recommending that is a zero-dependency alternative.compressionmiddleware (the standard Express solution, small and stable).node:zlibin a tiny hand-rolled middleware for the two JSON endpoints. More code to own; only worth it if avoiding the dependency matters more than the ~30 lines.Option 2 is the recommended implementation if compression is wanted; option 1's README note is the recommended minimum.
Suggested implementation (option 2)
npm install compression(+@types/compressionas a dev dependency).src/server/index.ts, mount it only for the API router and never for the SSE stream — compressingtext/event-streambuffers events and breaks live delivery:Notes for the implementer:
/api,request.pathis'/stream'for/api/stream— verify with a test rather than assuming (if it turns out to be the full path in this position, match accordingly).express.staticuncompressed or include it consciously — the JS bundle (~tens of KB) also benefits, but etag-based 304s already keep repeat loads cheap; compressing only/apiis the smaller, safer change./api/healthstay uncompressed automatically./api/bootstrapand/api/resync, and explicitly not for/api/stream.Acceptance criteria (if implemented as option 2)
GET /api/bootstrapwithAccept-Encoding: gzip→ response hasContent-Encoding: gzipand decodes to the same JSON as before.GET /api/stream→ noContent-Encoding, first SSE event arrives immediately (add/extend a route test asserting the header is absent).npm test,npm run typecheck, andnpm run buildpass;README.mddocuments the behavior briefly.