- Button was inside draggable div — browser started drag instead of click
- Button is now absolutely positioned outside the draggable div
- Outer div has position:relative for correct button placement
- Spacer span reserves space for the button in the layout
- Works on all screen sizes (desktop, tablet, mobile)
- Folders are draggable: drag folder into another folder (parent_id update)
- Circular reference prevention: isDescendantOrSelf() check
- Root drop zone: drag folder to root unparents it (parent_id=null)
- MoreVertical button: opacity-60 for mobile visibility (was opacity-0)
- Dropdown: viewport-clamped positioning + maxHeight with scroll
- ContactList already had draggable contacts (no changes needed)
wrap_plugin_route copied __signature__ from the original handler but
the wrapper's __globals__ namespace (plugin_error_handler.py) did not
contain the Pydantic models (ConversationCreate, MessageCreate, etc.).
FastAPI could not resolve ForwardRef('ConversationCreate') → 422 on
all POST routes with body parameters.
Removing the wrapper entirely fixes this. Plugin error isolation can
be re-added later using a different approach (middleware or exception handler).
Removing __annotations__ broke body parameter resolution: FastAPI could
not resolve ForwardRef('ConversationCreate') etc. causing 422 on all
POST routes with body params. Now keeping annotations from functools.wraps
and only removing return_annotation.
Root cause: session_cookie_samesite was 'strict' which prevents the
browser from sending the session cookie on WebSocket upgrade requests.
Changed to 'lax' which allows WebSocket cookies while still blocking
cross-site POST CSRF attacks.
Also removed debug logging from kommunikation routes.
Router-level dependencies=[Depends(require_active_plugin)] was applied
to ALL routes including WebSocket. Now adding the dependency per-HTTP-route
only, WebSocket routes are skipped entirely.
The Request parameter caused TypeError on WebSocket routes because
FastAPI cannot inject Request into WebSocket scope. Reverted to
parameterless _check(). WebSocket 403 is handled by CSRF middleware
which already skips WebSocket upgrade requests.
Simpler approach: require_active_plugin._check() now accepts Request
parameter and returns early for WebSocket upgrade requests.
No route splitting needed — all routes stay in their original router.
WebSocket routes were getting require_active_plugin dependency applied
via include_router(dependencies=[...]) which caused 403 Forbidden
before the WebSocket upgrade could happen.
Fix: Split router into HTTP routes (with dependency) and WebSocket routes
(registered separately without the active-plugin check). WebSocket auth
is handled inside the endpoint itself via session cookie verification.