fix: accept WebSocket before validation to prevent opaque 403 errors

All 5 WebSocket endpoints (expand, spec, assistant, terminal, project)
were closing the connection before calling accept() when validation
failed. Starlette converts pre-accept close into an HTTP 403, giving
clients no meaningful error information.

Server changes:
- Move websocket.accept() before all validation checks in every WS handler
- Send JSON error message before closing so clients get actionable errors
- Fix validate_project_name usage (raises HTTPException, not returns bool)
- ConnectionManager.connect() no longer calls accept() (caller's job)

Client changes:
- All 3 WS hooks (useWebSocket, useExpandChat, useSpecChat) skip
  reconnection on 4xxx close codes (application errors won't self-resolve)
- Gate expand button, keyboard shortcut, and modal on hasSpec
- Add hasSpec to useEffect dependency array to prevent stale closure
- Update keyboard shortcuts help text for E key context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nioasoft
2026-02-05 21:08:46 +02:00
parent f4facb3200
commit 035e8fdfca
12 changed files with 68 additions and 25 deletions

View File

@@ -178,8 +178,8 @@ function App() {
setShowAddFeature(true)
}
// E : Expand project with AI (when project selected and has features)
if ((e.key === 'e' || e.key === 'E') && selectedProject && features &&
// E : Expand project with AI (when project selected, has spec and has features)
if ((e.key === 'e' || e.key === 'E') && selectedProject && hasSpec && features &&
(features.pending.length + features.in_progress.length + features.done.length) > 0) {
e.preventDefault()
setShowExpandProject(true)
@@ -239,7 +239,7 @@ function App() {
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [selectedProject, showAddFeature, showExpandProject, selectedFeature, debugOpen, debugActiveTab, assistantOpen, features, showSettings, showKeyboardHelp, isSpecCreating, viewMode, showResetModal, wsState.agentStatus])
}, [selectedProject, showAddFeature, showExpandProject, selectedFeature, debugOpen, debugActiveTab, assistantOpen, features, showSettings, showKeyboardHelp, isSpecCreating, viewMode, showResetModal, wsState.agentStatus, hasSpec])
// Combine WebSocket progress with feature data
const progress = wsState.progress.total > 0 ? wsState.progress : {
@@ -490,7 +490,7 @@ function App() {
)}
{/* Expand Project Modal - AI-powered bulk feature creation */}
{showExpandProject && selectedProject && (
{showExpandProject && selectedProject && hasSpec && (
<ExpandProjectModal
isOpen={showExpandProject}
projectName={selectedProject}