Replace useRef-based previous pathname tracking (which violated
react-hooks/refs) with a derived state pattern. Sidebar state stores
{open, path} and sidebarOpen is computed as a pure expression,
avoiding both set-state-in-effect and refs-during-render lint errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Raise header to z-50 so toggle button stays clickable above backdrop
- Use document keydown listener for Escape instead of onKeyDown on backdrop
- Only render hamburger toggle when signed in
- Make SignedOutPanel col-span responsive (col-span-1 on mobile)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use ref-based previous pathname check instead of useEffect + setState,
which triggers the react-hooks/set-state-in-effect lint rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sidebar and backdrop were z-30, same as sticky page headers (live feed,
boards, etc.), causing the hamburger menu to render behind page content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add hamburger menu with off-canvas sidebar drawer on mobile.
Responsive padding, full-width panels, and stacked layouts
for screens under 768px (md breakpoint).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
node:20-alpine uses BusyBox which does not support GNU-style
--system/--ingroup flags. Switch to -S/-G equivalents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both backend and frontend Dockerfiles ran all processes as root.
Add a dedicated appuser in each runtime stage so container processes
run with minimal privileges, limiting blast radius of any container
escape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous fix fell back to the scheme's default port (443/80) when
url.port was empty, but url.port is empty for *both* 'wss://host:443'
and 'wss://host' — causing the validation to wrongly accept a URL with
no port at all.
Fix: inspect the raw authority segment of the URL string to check
whether a ':port' component is actually present, regardless of whether
that port is the scheme default.
Add gateway-form.test.ts covering:
- explicit non-default ports (accepted)
- explicit default ports :443 / :80 (accepted — regression case)
- missing port (rejected)
- wrong scheme (rejected)
- invalid URL (rejected)
- whitespace trimming
Closes#148
JavaScript's URL API omits .port for standard ports (443 for wss:,
80 for ws:) even when explicitly specified. This caused valid URLs
like wss://host.ts.net:443 to fail validation with 'Gateway URL
must include an explicit port.'
Fix by checking default ports when url.port is empty.
Closes#148