ZIm/crates/collab/src/db/tables
Nathan Sobo 0a2186c87b
Add channel reordering functionality (#31833)
Release Notes:

- Added channel reordering for administrators (use `cmd-up` and
`cmd-down` on macOS or `ctrl-up` `ctrl-down` on Linux to move channels
up or down within their parent)

## Summary

This PR introduces the ability for channel administrators to reorder
channels within their parent context, providing better organizational
control over channel hierarchies. Users can now move channels up or down
relative to their siblings using keyboard shortcuts.

## Problem

Previously, channels were displayed in alphabetical order with no way to
customize their arrangement. This made it difficult for teams to
organize channels in a logical order that reflected their workflow or
importance, forcing users to prefix channel names with numbers or
special characters as a workaround.

## Solution

The implementation adds a persistent `channel_order` field to channels
that determines their display order within their parent. Channels with
the same parent are sorted by this field rather than alphabetically.

## Implementation Details

### Database Schema

Added a new column and index to support efficient ordering:

```sql
-- crates/collab/migrations/20250530175450_add_channel_order.sql
ALTER TABLE channels ADD COLUMN channel_order INTEGER NOT NULL DEFAULT 1;

CREATE INDEX CONCURRENTLY "index_channels_on_parent_path_and_order" ON "channels" ("parent_path", "channel_order");
```

### RPC Protocol

Extended the channel proto with ordering support:

```proto
// crates/proto/proto/channel.proto
message Channel {
    uint64 id = 1;
    string name = 2;
    ChannelVisibility visibility = 3;
    int32 channel_order = 4;
    repeated uint64 parent_path = 5;
}

message ReorderChannel {
    uint64 channel_id = 1;
    enum Direction {
        Up = 0;
        Down = 1;
    }
    Direction direction = 2;
}
```

### Server-side Logic

The reordering is handled by swapping `channel_order` values between
adjacent channels:

```rust
// crates/collab/src/db/queries/channels.rs
pub async fn reorder_channel(
    &self,
    channel_id: ChannelId,
    direction: proto::reorder_channel::Direction,
    user_id: UserId,
) -> Result<Vec<Channel>> {
    // Find the sibling channel to swap with
    let sibling_channel = match direction {
        proto::reorder_channel::Direction::Up => {
            // Find channel with highest order less than current
            channel::Entity::find()
                .filter(
                    channel::Column::ParentPath
                        .eq(&channel.parent_path)
                        .and(channel::Column::ChannelOrder.lt(channel.channel_order)),
                )
                .order_by_desc(channel::Column::ChannelOrder)
                .one(&*tx)
                .await?
        }
        // Similar logic for Down...
    };
    
    // Swap the channel_order values
    let temp_order = channel.channel_order;
    channel.channel_order = sibling_channel.channel_order;
    sibling_channel.channel_order = temp_order;
}
```

### Client-side Sorting

Optimized the sorting algorithm to avoid O(n²) complexity:

```rust
// crates/collab/src/db/queries/channels.rs
// Pre-compute sort keys for efficient O(n log n) sorting
let mut channels_with_keys: Vec<(Vec<i32>, Channel)> = channels
    .into_iter()
    .map(|channel| {
        let mut sort_key = Vec::with_capacity(channel.parent_path.len() + 1);
        
        // Build sort key from parent path orders
        for parent_id in &channel.parent_path {
            sort_key.push(channel_order_map.get(parent_id).copied().unwrap_or(i32::MAX));
        }
        sort_key.push(channel.channel_order);
        
        (sort_key, channel)
    })
    .collect();

channels_with_keys.sort_by(|a, b| a.0.cmp(&b.0));
```

### User Interface

Added keyboard shortcuts and proper context handling:

```json
// assets/keymaps/default-macos.json
{
  "context": "CollabPanel && not_editing",
  "bindings": {
    "cmd-up": "collab_panel::MoveChannelUp",
    "cmd-down": "collab_panel::MoveChannelDown"
  }
}
```

The CollabPanel now properly sets context to distinguish between editing
and navigation modes:

```rust
// crates/collab_ui/src/collab_panel.rs
fn dispatch_context(&self, window: &Window, cx: &Context<Self>) -> KeyContext {
    let mut dispatch_context = KeyContext::new_with_defaults();
    dispatch_context.add("CollabPanel");
    dispatch_context.add("menu");
    
    let identifier = if self.channel_name_editor.focus_handle(cx).is_focused(window) {
        "editing"
    } else {
        "not_editing"
    };
    
    dispatch_context.add(identifier);
    dispatch_context
}
```

## Testing

Comprehensive tests were added to verify:
- Basic reordering functionality (up/down movement)
- Boundary conditions (first/last channels)
- Permission checks (non-admins cannot reorder)
- Ordering persistence across server restarts
- Correct broadcasting of changes to channel members

## Migration Strategy

Existing channels are assigned initial `channel_order` values based on
their current alphabetical sorting to maintain the familiar order users
expect:

```sql
UPDATE channels
SET channel_order = (
    SELECT ROW_NUMBER() OVER (
        PARTITION BY parent_path
        ORDER BY name, id
    )
    FROM channels c2
    WHERE c2.id = channels.id
);
```

## Future Enhancements

While this PR provides basic reordering functionality, potential future
improvements could include:
- Drag-and-drop reordering in the UI
- Bulk reordering operations
- Custom sorting strategies (by activity, creation date, etc.)

## Checklist

- [x] Database migration included
- [x] Tests added for new functionality
- [x] Keybindings work on macOS and Linux
- [x] Permissions properly enforced
- [x] Error handling implemented throughout
- [x] Manual testing completed
- [x] Documentation updated

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-06-04 16:56:33 +00:00
..
access_token.rs For impersonating access tokens, store impersonatee in the new column 2024-01-17 18:06:16 -08:00
billing_customer.rs collab: Limit customers to one free trial (#29232) 2025-04-22 20:41:17 +00:00
billing_preference.rs collab: Add LLM request overage columns to billing_preferences (#29446) 2025-04-25 20:43:43 +00:00
billing_subscription.rs collab: Use StripeClient in sync_subscription (#31761) 2025-05-30 16:08:58 +00:00
buffer.rs Denormalize buffer operations (#9026) 2024-03-07 11:35:47 -07:00
buffer_operation.rs Simplify buffer_operations schema 2023-08-23 18:37:01 -07:00
buffer_snapshot.rs Snapshot channel notes buffers when everyone leaves 2023-08-23 18:37:01 -07:00
channel.rs Add channel reordering functionality (#31833) 2025-06-04 16:56:33 +00:00
channel_buffer_collaborator.rs WIP 2023-08-23 18:34:43 -07:00
channel_chat_participant.rs Start work on restoring server-side code for chat messages 2023-09-07 16:32:49 -07:00
channel_member.rs chore: Bump Rust edition to 2024 (#27800) 2025-03-31 20:55:27 +02:00
channel_message.rs Channel chat: Add edit message (#9035) 2024-03-19 19:49:04 -06:00
channel_message_mention.rs Persist chat mentions 2023-10-18 16:56:03 -07:00
contact.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
contributor.rs Add REST APIs for getting and adding contributors 2024-01-22 10:48:33 -08:00
embedding.rs Semantic Index (#10329) 2024-04-12 11:40:59 -06:00
extension.rs Add an extensions API to the collaboration server (#7807) 2024-02-15 12:53:57 -08:00
extension_version.rs collab: Store features provided by extensions in the database (#24303) 2025-02-05 19:50:24 +00:00
feature_flag.rs collab: Allow enabling feature flags for all users (#16372) 2024-08-16 15:17:03 -04:00
follower.rs Undo making project optional on stored follower states 2023-09-28 14:21:44 -07:00
language_server.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
notification.rs Generalize notifications' actor id to entity id 2023-10-17 10:34:50 -07:00
notification_kind.rs Make notification db representation more flexible 2023-10-12 17:42:32 -07:00
observed_buffer_edits.rs Avoid N+1 query for channels with notes changes 2023-10-02 15:58:34 -07:00
observed_channel_messages.rs Add database implementation of channel message change tracking 2023-10-01 22:32:11 -07:00
processed_stripe_event.rs collab: Rework Stripe event processing (#15510) 2024-07-30 16:35:11 -04:00
project.rs Use anyhow more idiomatically (#31052) 2025-05-20 23:06:07 +00:00
project_collaborator.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
project_repository.rs project: Show detached head commit SHA in branch pickers (#29007) 2025-04-18 04:23:56 +05:30
project_repository_statuses.rs Separate repository state synchronization from worktree synchronization (#27140) 2025-03-20 18:07:03 -04:00
room.rs Remove environment guards (#7741) 2024-02-13 13:20:14 -07:00
room_participant.rs revert single channel click (#7738) 2024-02-13 12:53:49 -07:00
server.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
signup.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
user.rs Reduce the amount of queries performed when updating plan (#31268) 2025-05-23 12:03:50 +00:00
user_feature.rs Update database and RPC to provide configured feature flags 2023-08-25 14:34:32 -07:00
worktree.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
worktree_diagnostic_summary.rs Reorganize source files of collab::db 2023-08-18 16:23:33 -07:00
worktree_entry.rs Show project panel symlink icons for remote clients (#19464) 2024-10-19 19:44:47 +03:00
worktree_settings_file.rs debugger/tasks: Remove TaskType enum (#29208) 2025-04-26 01:44:56 +02:00