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:
egg
2025-12-05 09:12:10 +08:00
parent 1e44a63a8e
commit 1d5d4d447d
48 changed files with 3505 additions and 401 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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