Skip to content

Comments

updates labels system#1519

Closed
ahmetskilinc wants to merge 2 commits intostagingfrom
new-labels-system
Closed

updates labels system#1519
ahmetskilinc wants to merge 2 commits intostagingfrom
new-labels-system

Conversation

@ahmetskilinc
Copy link
Contributor

@ahmetskilinc ahmetskilinc commented Jun 27, 2025

Add drag-and-drop reordering for labels

This PR implements a drag-and-drop interface for reordering labels in the settings page. Labels can now be visually rearranged using a drag handle, and their order is persisted to the database. The order is respected throughout the application, including in the sidebar navigation.

Technical changes:

  • Added @dnd-kit for drag-and-drop functionality in the labels settings page
  • Created a new database table mail0_label_order to store label ordering and custom colors
  • Added server-side endpoints to fetch and update label orders
  • Modified label creation to assign random colors when none are specified
  • Updated sidebar to respect the saved label order when displaying labels

Summary by CodeRabbit

  • New Features

    • Labels can now be reordered via drag-and-drop in the settings interface.
    • Label order is saved and synced across devices.
    • Sidebar and label lists now respect custom label ordering.
    • Added support for custom label colors with server-assigned defaults.
  • Bug Fixes

    • Improved sorting of folder groups and labels based on explicit order rather than alphabetical order.
  • Chores

    • Database updated to support label ordering and custom colors.
    • New translation messages for label reordering success and failure.

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 27, 2025

Walkthrough

This update introduces label ordering and drag-and-drop reordering functionality for mail labels. It adds a sortable list UI using @dnd-kit, integrates label order persistence with the backend and database, extends label types with an order property, and updates queries, mutations, and UI components to support and display label ordering throughout the application.

Changes

File(s) Change Summary
apps/mail/app/(routes)/settings/labels/page.tsx Replaces static label grid with drag-and-drop sortable list using @dnd-kit; adds SortableLabelItem component; updates label management logic.
apps/mail/components/labels/label-dialog.tsx Changes default color values for new labels to empty strings; minor import and formatting tweaks; adds clarifying comment.
apps/mail/components/ui/sidebar-labels.tsx Adds explicit ordering for folder groups and bracketed labels based on label order property.
apps/mail/hooks/use-label-orders.ts Adds new useLabelOrders React hook for fetching/updating label orders with React Query and TRPC.
apps/mail/hooks/use-labels.ts Adds comment clarifying server-side label sorting.
apps/mail/types/index.ts Adds optional order?: number property to Label type.
apps/server/package.json Adds nanoid dependency.
apps/server/src/db/migrations/0035_shocking_green_goblin.sql Adds migration creating mail0_label_order table for label ordering and custom color data.
apps/server/src/db/migrations/0036_bizarre_bushwacker.sql Adds migration creating mail0_label_order table with constraints and indexes.
apps/server/src/db/migrations/0037_dazzling_queen_noir.sql Adds migration creating mail0_label_order table, updates foreign keys with cascade deletes, and adds multiple indexes.
apps/server/src/db/migrations/meta/0035_snapshot.json Adds migration snapshot defining schema for all tables, including new label order table.
apps/server/src/db/migrations/meta/0036_snapshot.json Adds migration snapshot defining schema for multiple tables including label order.
apps/server/src/db/migrations/meta/_journal.json Adds migration journal entry for new migration.
apps/server/src/db/schema.ts Adds exported labelOrder table definition with order, custom color, and constraints.
apps/server/src/main.ts Adds labelOrder to imported schema entities; adds methods for getting and updating label orders in DB classes.
apps/server/src/routes/chat.ts Adds getLabelOrders and updateLabelOrders methods to agent classes for label order persistence; minor SQL formatting.
apps/server/src/trpc/routes/label.ts Enhances labels router: merges order/color into labels, adds reorder and getOrders procedures, updates create logic for order/color.
apps/mail/messages/en.json Reorders label-related translation keys, removes some deletion confirmation keys, adds messages for reorder success/failure.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LabelsPage (UI)
    participant useLabelOrders Hook
    participant TRPC/Backend
    participant Database

    User->>LabelsPage (UI): Drag label to reorder
    LabelsPage (UI)->>useLabelOrders Hook: Call updateOrders(newOrder)
    useLabelOrders Hook->>TRPC/Backend: Mutation: reorder(labelOrders)
    TRPC/Backend->>Database: Update mail0_label_order with new orders
    Database-->>TRPC/Backend: Confirm update
    TRPC/Backend-->>useLabelOrders Hook: Success response
    useLabelOrders Hook-->>LabelsPage (UI): Refetch orders/labels
    LabelsPage (UI)-->>User: Updated label order displayed
Loading

Suggested reviewers

  • MrgSub

Poem

In the burrows of code where the labels reside,
A rabbit hopped in, with new order applied.
Drag and drop magic, the labels align—
Colors and numbers, all sorted just fine.
With backend and frontend now working in sync,
This bunny’s delighted—what do you think? 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95f91f1 and 96010ff.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • apps/server/package.json (1 hunks)
  • apps/server/src/routes/chat.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/server/package.json
  • apps/server/src/routes/chat.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript-typescript)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ahmetskilinc ahmetskilinc marked this pull request as ready for review June 27, 2025 15:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
apps/server/package.json (1)

60-60: Pinning nanoid without caret may hinder patch-level security updates

"nanoid": "5.1.5" is added as a strict version. This prevents npm from applying non-breaking patch upgrades automatically (e.g. 5.1.x). Unless you have a reproducibility requirement, consider:

-    "nanoid": "5.1.5",
+    "nanoid": "^5.1.5",

Also double-check whether uuid is still needed—it overlaps with nanoid for ID generation.

apps/mail/hooks/use-labels.ts (1)

12-14: Relying solely on backend ordering makes the UI fragile

The comment says the server already sorts the labels. If that assumption ever breaks (e.g. cache fallback, older client hitting newer server), the UI will silently render unsorted data. A cheap safeguard is to sort defensively on the client:

-  // Labels are already sorted by order from the server
-  return labelQuery;
+  // Server should send pre-sorted labels, but sort defensively just in case
+  return {
+    ...labelQuery,
+    data: labelQuery.data?.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0)),
+  };

This keeps behaviour correct even when the contract drifts.

apps/mail/types/index.ts (1)

10-10: Optional order should be nullable for clearer semantics

Most DB rows will have a numeric order, while system labels (Inbox, Sent, …) may intentionally have NULL. Explicitly declaring this helps TypeScript catch misuse:

-  order?: number;
+  order?: number | null;

This also matches how Drizzle/SQL returns nullable columns.

apps/server/src/trpc/routes/label.ts (1)

201-212: Consider returning empty object instead of null.

Returning null when no orders exist might require additional null checks on the client side.

Consider always returning an object for consistency:

-return Object.keys(ordersMap).length > 0 ? ordersMap : null;
+return ordersMap;
apps/server/src/routes/chat.ts (1)

744-776: Optimize ID generation in updateLabelOrders.

Currently generating a new ID for each upsert operation, even when updating existing records.

Consider checking if the record exists before generating a new ID:

await db.transaction(async (tx) => {
  for (const { id: labelId, order } of labelOrders) {
+    // Check if record exists
+    const existing = await tx
+      .select({ id: labelOrder.id })
+      .from(labelOrder)
+      .where(and(
+        eq(labelOrder.connectionId, connectionId),
+        eq(labelOrder.labelId, labelId)
+      ))
+      .limit(1);
+    
    await tx
      .insert(labelOrder)
      .values({
-        id: nanoid(),
+        id: existing[0]?.id || nanoid(),
        connectionId,
        labelId,
        order,
      })
      .onConflictDoUpdate({
        target: [labelOrder.connectionId, labelOrder.labelId],
        set: {
          order,
          updatedAt: new Date(),
        },
      });
  }
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70a434c and 0b975b1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/mail/app/(routes)/settings/labels/page.tsx (5 hunks)
  • apps/mail/components/labels/label-dialog.tsx (4 hunks)
  • apps/mail/components/ui/sidebar-labels.tsx (3 hunks)
  • apps/mail/hooks/use-label-orders.ts (1 hunks)
  • apps/mail/hooks/use-labels.ts (1 hunks)
  • apps/mail/locales/en.json (1 hunks)
  • apps/mail/types/index.ts (1 hunks)
  • apps/server/package.json (1 hunks)
  • apps/server/src/db/migrations/0035_shocking_green_goblin.sql (1 hunks)
  • apps/server/src/db/migrations/meta/0035_snapshot.json (1 hunks)
  • apps/server/src/db/migrations/meta/_journal.json (1 hunks)
  • apps/server/src/db/schema.ts (1 hunks)
  • apps/server/src/main.ts (1 hunks)
  • apps/server/src/routes/chat.ts (9 hunks)
  • apps/server/src/trpc/routes/label.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
apps/mail/components/labels/label-dialog.tsx (1)
Learnt from: snehendu098
PR: Mail-0/Zero#1323
File: apps/mail/lib/themes/theme-utils.ts:318-318
Timestamp: 2025-06-24T06:22:58.753Z
Learning: In the Mail-0/Zero theme system (apps/mail/lib/themes/theme-utils.ts), when color themes are being applied, all color values come in HSL format, so there's no need for additional format validation when converting colors with hslToHex().
apps/mail/components/ui/sidebar-labels.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
apps/mail/app/(routes)/settings/labels/page.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
🔇 Additional comments (12)
apps/mail/hooks/use-label-orders.ts (1)

1-16: LGTM! Clean and well-structured hook implementation.

The hook follows React Query and TRPC best practices with appropriate caching strategy and clean API design.

apps/server/src/db/schema.ts (1)

214-234: Well-designed schema for label ordering.

The labelOrder table has proper foreign key relationships, appropriate defaults, and necessary constraints. The high default order value (999999) is a good pattern for allowing new items to appear at the end.

apps/mail/components/ui/sidebar-labels.tsx (4)

50-50: Good addition of folder order tracking.

The folderOrder record properly tracks the order of folder groups based on the first label encountered in each group.


71-75: Correct sorting implementation for folder groups.

The sorting logic properly orders folder groups by their tracked order values with appropriate fallback to a high number (999999) for consistent behavior.


81-88: Proper label sorting within groups.

Labels are correctly sorted by their order property before mapping, ensuring consistent ordering within each folder group.


123-130: Consistent sorting for bracketed labels.

The bracketed labels in the "Other" folder are sorted using the same pattern as other label groups, maintaining consistency across the UI.

apps/mail/components/labels/label-dialog.tsx (2)

67-67: Good alignment with server-side color assignment.

Changing the default color to empty strings allows the server to assign random colors for new labels, which is consistent with the new label management system.


70-73: Clear documentation of the color assignment strategy.

The comment and empty default values properly communicate that the server handles color assignment for new labels.

apps/mail/locales/en.json (1)

347-348: Appropriate localization for label reordering feature.

The success and failure messages for label reordering are clear, consistent with existing patterns, and provide good user feedback.

apps/mail/app/(routes)/settings/labels/page.tsx (1)

205-220: Let’s inspect both updateLabelOrders definitions in chat.ts and trace the mainDo implementation to see if transactions or locking are used:

#!/bin/bash
# 1. Show the route handler at line ~744 in chat.ts
echo "----- chat.ts (lines 730-800) -----"
sed -n '730,800p' apps/server/src/routes/chat.ts

# 2. Find where `mainDo` is imported in chat.ts
echo "----- mainDo import -----"
grep -R "import .*mainDo" -n apps/server/src/routes/chat.ts

# 3. Locate the service file implementing updateLabelOrders in mainDo
#    (replace the path below once we see the import)
# 4. Once we have the file, dump its updateLabelOrders body
#    For now, search for the method across the entire server directory:
echo "----- service implementation search -----"
rg -n "updateLabelOrders" apps/server/src
apps/server/src/trpc/routes/label.ts (1)

55-62: LGTM! Well-implemented label ordering logic.

The implementation correctly merges label orders with labels, applies custom colors, and sorts by order with a sensible default value.

apps/server/src/db/migrations/meta/0035_snapshot.json (1)

1-1195: Auto-generated migration snapshot looks correct.

The schema definition for mail0_label_order table matches the SQL migration file, including all constraints and defaults.

Copy link
Collaborator

@MrgSub MrgSub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coderabbit comments

@graphite-app
Copy link
Contributor

graphite-app bot commented Jun 30, 2025

Merge activity

  • Jun 30, 6:47 PM UTC: This pull request can not be added to the Graphite merge queue. Please try rebasing and resubmitting to merge when ready.
  • Jun 30, 6:47 PM UTC: Graphite disabled "merge when ready" on this PR due to: a merge conflict with the target branch; resolve the conflict and try again..
  • Jul 4, 6:35 PM UTC: This pull request can not be added to the Graphite merge queue. Please try rebasing and resubmitting to merge when ready.
  • Jul 4, 6:35 PM UTC: Graphite disabled "merge when ready" on this PR due to: a merge conflict with the target branch; resolve the conflict and try again..
  • Jul 8, 5:14 PM UTC: This pull request can not be added to the Graphite merge queue. Please try rebasing and resubmitting to merge when ready.
  • Jul 8, 5:14 PM UTC: Graphite disabled "merge when ready" on this PR due to: a merge conflict with the target branch; resolve the conflict and try again..

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/mail/app/(routes)/settings/labels/page.tsx (2)

65-67: Handle undefined label.id more safely.

Using label.id || '' could cause issues if the label ID is legitimately undefined.

Consider adding validation:

-const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
-  id: label.id || '',
-});
+if (!label.id) {
+  console.error('Label without ID detected:', label);
+  return null;
+}
+const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+  id: label.id,
+});

121-121: Avoid non-null assertion operator.

The non-null assertion label.id! could cause runtime errors.

Since we're already checking for label.id existence above (after applying the previous fix), we can safely use it:

-onClick={() => onDelete(label.id!)}
+onClick={() => onDelete(label.id)}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b975b1 and 7640761.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/mail/app/(routes)/settings/labels/page.tsx (5 hunks)
  • apps/mail/components/labels/label-dialog.tsx (5 hunks)
  • apps/mail/components/ui/sidebar-labels.tsx (3 hunks)
  • apps/mail/hooks/use-label-orders.ts (1 hunks)
  • apps/mail/hooks/use-labels.ts (1 hunks)
  • apps/mail/messages/en.json (1 hunks)
  • apps/mail/types/index.ts (1 hunks)
  • apps/server/package.json (1 hunks)
  • apps/server/src/db/migrations/0035_shocking_green_goblin.sql (1 hunks)
  • apps/server/src/db/migrations/meta/0035_snapshot.json (1 hunks)
  • apps/server/src/db/migrations/meta/_journal.json (1 hunks)
  • apps/server/src/db/schema.ts (1 hunks)
  • apps/server/src/main.ts (1 hunks)
  • apps/server/src/routes/chat.ts (5 hunks)
  • apps/server/src/trpc/routes/label.ts (4 hunks)
✅ Files skipped from review due to trivial changes (3)
  • apps/server/src/main.ts
  • apps/server/package.json
  • apps/mail/messages/en.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • apps/mail/hooks/use-labels.ts
  • apps/server/src/db/migrations/meta/_journal.json
  • apps/mail/types/index.ts
  • apps/mail/hooks/use-label-orders.ts
  • apps/mail/components/ui/sidebar-labels.tsx
  • apps/server/src/db/schema.ts
  • apps/mail/components/labels/label-dialog.tsx
  • apps/server/src/db/migrations/0035_shocking_green_goblin.sql
  • apps/server/src/routes/chat.ts
  • apps/server/src/db/migrations/meta/0035_snapshot.json
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
apps/mail/app/(routes)/settings/labels/page.tsx (1)
Learnt from: retrogtx
PR: Mail-0/Zero#1328
File: apps/mail/lib/hotkeys/mail-list-hotkeys.tsx:202-209
Timestamp: 2025-06-18T17:26:50.918Z
Learning: In apps/mail/lib/hotkeys/mail-list-hotkeys.tsx, the switchCategoryByIndex function using hardcoded indices for category hotkeys does not break when users reorder categories, contrary to the theoretical index-shifting issue. The actual implementation has constraints or mechanisms that prevent hotkey targeting issues.
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
apps/server/src/trpc/routes/label.ts (1)

198-206: Good validation implementation!

The validation for non-negative and unique order values properly addresses potential data integrity issues.

dnd labels in label settings

fixes rebase
@ahmetskilinc ahmetskilinc requested a review from MrgSub July 5, 2025 13:48
@MrgSub MrgSub closed this Aug 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants