feat: Add mobile responsive layout, open room access, and admin room management
Mobile Responsive Layout: - Add useMediaQuery, useIsMobile, useIsTablet, useIsDesktop hooks for device detection - Create MobileHeader component with hamburger menu and action drawer - Create BottomToolbar for mobile navigation (Files, Members) - Create SlidePanel component for full-screen mobile sidebars - Update RoomDetail.tsx with mobile/desktop conditional rendering - Update RoomList.tsx with single-column grid and touch-friendly buttons - Add CSS custom properties for safe areas and touch targets (min 44px) - Add mobile viewport meta tags for notched devices Open Room Access: - All authenticated users can view all rooms (not just their own) - Users can join active rooms they're not members of - Add is_member field to room responses - Update room list API to return all rooms by default Admin Room Management: - Add permanent delete functionality for system admins - Add delete confirmation dialog with room title verification - Broadcast room deletion via WebSocket to connected users - Add users search API for adding members 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Proposal: Add Open Room Access
|
||||
|
||||
## Summary
|
||||
Modify the room access model to allow all authenticated users to view and self-join rooms, while maintaining role-based permissions for room operations.
|
||||
|
||||
## Motivation
|
||||
The current room model requires explicit invitation for users to see and join rooms. This creates friction in incident response scenarios where speed is critical. Users should be able to:
|
||||
1. Discover all active incidents without needing an invitation
|
||||
2. Self-join rooms to contribute or observe
|
||||
3. Have their role upgraded by existing members
|
||||
|
||||
## Scope
|
||||
|
||||
### Backend Changes (chat-room spec)
|
||||
1. **Public Room Listing**: All authenticated users can view all rooms (not just their own)
|
||||
2. **Self-Join Mechanism**: New endpoint `POST /api/rooms/{room_id}/join` for self-joining as VIEWER
|
||||
3. **Role Upgrade Permission**: EDITOR role gains permission to upgrade VIEWER → EDITOR
|
||||
|
||||
### Frontend Changes (frontend-core spec)
|
||||
1. **Member Search**: Add user search functionality when inviting/managing members
|
||||
2. **Password Visibility Toggle**: Add show/hide password button on login form
|
||||
3. **Join Room Button**: Display "Join" button for rooms where user is not a member
|
||||
|
||||
## Out of Scope
|
||||
- Creating private/invite-only room types (future enhancement)
|
||||
- Role downgrade by EDITOR (only OWNER can downgrade)
|
||||
- Member removal by EDITOR (only OWNER can remove)
|
||||
|
||||
## Related Specs
|
||||
- `chat-room`: Room membership and access control
|
||||
- `frontend-core`: Login and member management UI
|
||||
@@ -0,0 +1,103 @@
|
||||
# chat-room Specification Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: List and Filter Incident Rooms
|
||||
The system SHALL provide endpoints to list incident rooms with filtering capabilities. All authenticated users SHALL be able to view all rooms regardless of membership status.
|
||||
|
||||
#### Scenario: List all rooms for authenticated user (MODIFIED)
|
||||
- **WHEN** an authenticated user sends `GET /api/rooms`
|
||||
- **THEN** the system SHALL return ALL rooms in the system
|
||||
- **AND** include room metadata (title, type, severity, member count, last activity)
|
||||
- **AND** include `is_member` flag indicating if user is a member
|
||||
- **AND** include `current_user_role` (null if not a member)
|
||||
- **AND** sort by last_activity_at descending (most recent first)
|
||||
|
||||
#### Scenario: Filter rooms with membership filter
|
||||
- **WHEN** a user sends `GET /api/rooms?my_rooms=true`
|
||||
- **THEN** the system SHALL return only rooms where the user is a member
|
||||
- **AND** apply any other filters (status, incident_type, etc.)
|
||||
|
||||
### Requirement: Room Self-Join
|
||||
The system SHALL allow any authenticated user to join a room as a VIEWER without requiring an invitation.
|
||||
|
||||
#### Scenario: Self-join room as viewer
|
||||
- **WHEN** an authenticated non-member sends `POST /api/rooms/{room_id}/join`
|
||||
- **THEN** the system SHALL create a room_members record with role "viewer"
|
||||
- **AND** update room's member_count
|
||||
- **AND** record added_by as the joining user's own ID
|
||||
- **AND** record added_at timestamp
|
||||
- **AND** return status 200 with the new membership details
|
||||
|
||||
#### Scenario: Self-join when already a member
|
||||
- **WHEN** an existing member sends `POST /api/rooms/{room_id}/join`
|
||||
- **THEN** the system SHALL return status 409 with "Already a member of this room"
|
||||
- **AND** include current membership details in response
|
||||
|
||||
#### Scenario: Self-join archived room
|
||||
- **WHEN** a user attempts to join an archived room
|
||||
- **THEN** the system SHALL return status 400 with "Cannot join archived room"
|
||||
|
||||
### Requirement: Manage Room Membership (MODIFIED)
|
||||
The system SHALL allow room owners and editors to manage members. Editors SHALL be able to upgrade VIEWER members to EDITOR role but cannot downgrade or remove members.
|
||||
|
||||
#### Scenario: Editor upgrades viewer to editor (NEW)
|
||||
- **WHEN** a room editor sends `PATCH /api/rooms/{room_id}/members/{user_id}` with:
|
||||
```json
|
||||
{
|
||||
"role": "editor"
|
||||
}
|
||||
```
|
||||
- **AND** the target user is currently a viewer
|
||||
- **THEN** the system SHALL update the member's role to "editor"
|
||||
- **AND** record the change in audit log
|
||||
- **AND** return status 200 with updated member details
|
||||
|
||||
#### Scenario: Editor attempts to downgrade member
|
||||
- **WHEN** a room editor attempts to change a member's role to a lower role (editor → viewer)
|
||||
- **THEN** the system SHALL return status 403 with "Editors can only upgrade members"
|
||||
|
||||
#### Scenario: Editor attempts to remove member
|
||||
- **WHEN** a room editor attempts to remove a member
|
||||
- **THEN** the system SHALL return status 403 with "Only owner can remove members"
|
||||
|
||||
#### Scenario: Editor attempts to set owner role
|
||||
- **WHEN** a room editor attempts to change a member's role to "owner"
|
||||
- **THEN** the system SHALL return status 403 with "Only owner can transfer ownership"
|
||||
|
||||
### Requirement: Room Access Control (MODIFIED)
|
||||
The system SHALL enforce role-based access control. Non-members can view room metadata in listings but must join to access room content.
|
||||
|
||||
#### Scenario: Non-member views room in list (NEW)
|
||||
- **WHEN** a non-member requests room list via `GET /api/rooms`
|
||||
- **THEN** the system SHALL include all rooms with basic metadata
|
||||
- **AND** set `is_member: false` and `current_user_role: null` for non-member rooms
|
||||
|
||||
#### Scenario: Non-member attempts to access room details
|
||||
- **WHEN** a non-member sends `GET /api/rooms/{room_id}`
|
||||
- **THEN** the system SHALL return status 403 with "Join room to access details"
|
||||
- **AND** include a `join_url` field pointing to the join endpoint
|
||||
|
||||
#### Scenario: Non-member attempts to access room messages
|
||||
- **WHEN** a non-member sends `GET /api/rooms/{room_id}/messages`
|
||||
- **THEN** the system SHALL return status 403 with "Not a member of this room"
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: User Directory Search
|
||||
The system SHALL provide a searchable user directory for member management, sourced from the users table (populated during login).
|
||||
|
||||
#### Scenario: Search users by name or email
|
||||
- **WHEN** a room owner or editor sends `GET /api/users/search?q=john`
|
||||
- **THEN** the system SHALL return users matching the search query
|
||||
- **AND** search both display_name and user_id (email) fields
|
||||
- **AND** return at most 20 results
|
||||
- **AND** include user_id and display_name for each result
|
||||
|
||||
#### Scenario: Search with empty query
|
||||
- **WHEN** a user sends `GET /api/users/search` without query parameter
|
||||
- **THEN** the system SHALL return status 400 with "Search query required"
|
||||
|
||||
#### Scenario: Search returns no results
|
||||
- **WHEN** a search query matches no users
|
||||
- **THEN** the system SHALL return an empty array with status 200
|
||||
@@ -0,0 +1,116 @@
|
||||
# frontend-core Specification Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: User Authentication Interface (MODIFIED)
|
||||
The frontend SHALL provide a login interface with password visibility toggle.
|
||||
|
||||
#### Scenario: Password visibility toggle (NEW)
|
||||
- **WHEN** a user is on the login page
|
||||
- **THEN** the system SHALL:
|
||||
- Display password field with masked input by default
|
||||
- Show a toggle button (eye icon) next to the password field
|
||||
- Toggle password visibility when button is clicked
|
||||
- Change icon to indicate current state (eye/eye-slash)
|
||||
|
||||
#### Scenario: Password field default state
|
||||
- **WHEN** the login page loads
|
||||
- **THEN** the password field SHALL be in masked (hidden) state
|
||||
- **AND** the toggle button SHALL show "show password" icon
|
||||
|
||||
### Requirement: Incident Room List (MODIFIED)
|
||||
The frontend SHALL display all rooms with join capability for non-member rooms.
|
||||
|
||||
#### Scenario: Display all rooms including non-member rooms (MODIFIED)
|
||||
- **WHEN** a logged-in user navigates to the room list page
|
||||
- **THEN** the system SHALL:
|
||||
- Fetch all rooms from `GET /api/rooms`
|
||||
- Display rooms as cards with title, status, severity, and timestamp
|
||||
- Show "Member" badge for rooms where user is a member
|
||||
- Show "Join" button for rooms where user is not a member
|
||||
- Order by last activity (most recent first)
|
||||
|
||||
#### Scenario: Filter to show only my rooms
|
||||
- **WHEN** a user toggles "My Rooms Only" filter
|
||||
- **THEN** the system SHALL:
|
||||
- Add `?my_rooms=true` parameter to room list request
|
||||
- Display only rooms where user is a member
|
||||
- Hide the filter when active (or show "Show All" toggle)
|
||||
|
||||
#### Scenario: Join room from list
|
||||
- **WHEN** a user clicks "Join" button on a room card
|
||||
- **THEN** the system SHALL:
|
||||
- Send `POST /api/rooms/{room_id}/join`
|
||||
- Update card to show "Member" badge on success
|
||||
- Navigate to room detail page
|
||||
- Show error message on failure
|
||||
|
||||
### Requirement: Member Management Interface (MODIFIED)
|
||||
The frontend SHALL provide member management with user search functionality.
|
||||
|
||||
#### Scenario: Add member with search (MODIFIED)
|
||||
- **WHEN** an owner or editor opens the add member dialog
|
||||
- **THEN** the system SHALL:
|
||||
- Display a searchable user input field
|
||||
- Query `GET /api/users/search?q={query}` as user types (debounced)
|
||||
- Display matching users in a dropdown list
|
||||
- Allow selection of a user from the list
|
||||
- Show role selection dropdown (default: viewer)
|
||||
- Allow role upgrade to editor for existing viewers
|
||||
|
||||
#### Scenario: Search users while adding member
|
||||
- **WHEN** a user types in the member search field
|
||||
- **THEN** the system SHALL:
|
||||
- Wait 300ms after last keystroke (debounce)
|
||||
- Show loading indicator
|
||||
- Display search results with display_name and email
|
||||
- Allow clicking to select a user
|
||||
|
||||
#### Scenario: No search results
|
||||
- **WHEN** user search returns no results
|
||||
- **THEN** the system SHALL:
|
||||
- Display "No users found" message
|
||||
- Suggest checking the spelling or trying a different search
|
||||
|
||||
#### Scenario: Editor upgrades viewer to editor (NEW)
|
||||
- **WHEN** an editor views a viewer member's options
|
||||
- **THEN** the system SHALL:
|
||||
- Display "Upgrade to Editor" option
|
||||
- Submit role change to `PATCH /api/rooms/{room_id}/members/{user_id}`
|
||||
- Update member display on success
|
||||
- Show error message on failure
|
||||
|
||||
#### Scenario: Editor cannot downgrade or remove (NEW)
|
||||
- **WHEN** an editor views member management options
|
||||
- **THEN** the system SHALL:
|
||||
- Hide "Remove" option for all members
|
||||
- Hide role downgrade options
|
||||
- Only show "Upgrade to Editor" for viewers
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Room Join Interface
|
||||
The frontend SHALL provide UI for non-members to join rooms.
|
||||
|
||||
#### Scenario: View room as non-member
|
||||
- **WHEN** a non-member navigates to a room they found in the list
|
||||
- **THEN** the system SHALL:
|
||||
- Display room basic info (title, status, severity)
|
||||
- Show "Join Room" prominent button
|
||||
- Display message explaining they need to join to view content
|
||||
- Show member count and activity summary
|
||||
|
||||
#### Scenario: Join room from detail page
|
||||
- **WHEN** a non-member clicks "Join Room" button
|
||||
- **THEN** the system SHALL:
|
||||
- Send `POST /api/rooms/{room_id}/join`
|
||||
- Reload room with full content access on success
|
||||
- Display success toast "You have joined the room as Viewer"
|
||||
- Show error message on failure
|
||||
|
||||
#### Scenario: Cannot join archived room
|
||||
- **WHEN** a non-member views an archived room
|
||||
- **THEN** the system SHALL:
|
||||
- Display "This room is archived" message
|
||||
- Hide the "Join Room" button
|
||||
- Show room metadata in read-only mode
|
||||
@@ -0,0 +1,89 @@
|
||||
# Tasks: Add Open Room Access
|
||||
|
||||
## Phase 1: Backend - Room Visibility & Self-Join
|
||||
|
||||
### 1.1 Modify room listing to show all rooms
|
||||
- [x] Update `room_service.list_user_rooms()` to return all rooms for authenticated users
|
||||
- [x] Add `is_member` and `current_user_role` fields to room response
|
||||
- [x] Add `my_rooms` query parameter filter
|
||||
- [x] Update room list schema to include new fields
|
||||
- [x] Write unit tests for modified listing behavior
|
||||
|
||||
### 1.2 Implement self-join endpoint
|
||||
- [x] Create `POST /api/rooms/{room_id}/join` endpoint
|
||||
- [x] Add validation for already-member case (return 409)
|
||||
- [x] Add validation for archived room case (return 400)
|
||||
- [x] Create membership with role="viewer" and added_by=self
|
||||
- [x] Update room member_count on join
|
||||
- [x] Write integration tests for self-join
|
||||
|
||||
### 1.3 Modify role change permissions for editors
|
||||
- [x] Update `membership_service.check_user_permission()` for role changes
|
||||
- [x] Allow EDITOR to upgrade VIEWER → EDITOR
|
||||
- [x] Deny EDITOR from downgrading (editor→viewer) or removing members
|
||||
- [x] Deny EDITOR from setting owner role
|
||||
- [x] Write unit tests for permission matrix changes
|
||||
|
||||
### 1.4 Implement user search endpoint
|
||||
- [x] Create `GET /api/users/search` endpoint
|
||||
- [x] Query users table by display_name and user_id (email)
|
||||
- [x] Return max 20 results
|
||||
- [x] Require minimum query length
|
||||
- [x] Write tests for search functionality
|
||||
|
||||
## Phase 2: Frontend - Login & Room List
|
||||
|
||||
### 2.1 Add password visibility toggle to login
|
||||
- [x] Add eye/eye-slash toggle button to password field
|
||||
- [x] Toggle input type between "password" and "text"
|
||||
- [x] Update button icon based on visibility state
|
||||
- [x] Ensure toggle works with keyboard accessibility
|
||||
|
||||
### 2.2 Update room list for all-rooms view
|
||||
- [x] Fetch all rooms (remove member-only filter default)
|
||||
- [x] Display "Member" badge for member rooms
|
||||
- [x] Display "Join" button for non-member rooms
|
||||
- [x] Add "My Rooms Only" filter toggle
|
||||
- [x] Handle join action with optimistic update
|
||||
|
||||
### 2.3 Create room join preview for non-members
|
||||
- [x] Create restricted view for non-member room access
|
||||
- [x] Show room metadata but not content
|
||||
- [x] Display prominent "Join Room" button
|
||||
- [x] Handle join with success toast and page reload
|
||||
|
||||
## Phase 3: Frontend - Member Management
|
||||
|
||||
### 3.1 Add user search to member management
|
||||
- [x] Create searchable user input component
|
||||
- [x] Implement debounced search (300ms)
|
||||
- [x] Display search results with name and email
|
||||
- [x] Handle empty results state
|
||||
- [x] Wire up to `GET /api/users/search`
|
||||
|
||||
### 3.2 Update member role change UI for editors
|
||||
- [x] Show "Upgrade to Editor" for viewers (when current user is editor)
|
||||
- [x] Hide remove option for editors
|
||||
- [x] Hide downgrade options for editors
|
||||
- [x] Keep full controls visible for owners
|
||||
|
||||
## Phase 4: Testing & Validation
|
||||
|
||||
### 4.1 Backend integration tests
|
||||
- [x] Test room listing shows all rooms
|
||||
- [x] Test self-join creates viewer membership
|
||||
- [x] Test editor can upgrade but not downgrade
|
||||
- [x] Test user search returns correct results
|
||||
|
||||
### 4.2 Frontend E2E tests
|
||||
- [x] Test password visibility toggle
|
||||
- [x] Test room list shows join buttons
|
||||
- [x] Test self-join flow
|
||||
- [x] Test member search and add flow
|
||||
- [x] Test editor role limitations
|
||||
|
||||
## Validation Checklist
|
||||
- [x] Run `openspec validate add-open-room-access --strict`
|
||||
- [x] All existing tests pass
|
||||
- [x] New tests cover all scenarios
|
||||
- [x] Manual testing of full user flow
|
||||
Reference in New Issue
Block a user