Skip to content

Conversation

@kodjima33
Copy link
Collaborator

Feature Request

Implement right-swipe on mobile task items to create a subtask, mirroring macOS Tab behavior.

Requirements:

  • Platform: Flutter app in based-hardware/omi repo.
  • Scope: Only mobile (iOS/Android) task list UI. Do NOT change macOS behavior, only reuse its logic.
  • Behavior:
    • In the mobile task list (where tapping a task opens details and checkboxes toggle completion), add a right-swipe gesture on a task row.
    • Swiping right on a task should convert that task into a subtask of the task immediately above it in the list, following the same rules as macOS when pressing Tab on a task:
      • If there is a task above, current task becomes its child/subtask, one level deeper in the hierarchy.
      • If there is no task above (top of list), do nothing.
      • Maintain ordering semantics consistent with macOS implementation.
    • If macOS currently uses a shared function/util for "indent/make subtask", call that same function from the mobile swipe handler so logic stays consistent.
  • UX details:
    • Use the same swipe pattern that already exists elsewhere in the app for destructive/non-destructive actions, if present (e.g., any existing Slidable/Dismissible conventions). If not, implement a simple, fast right-swipe interaction that:
      • Shows a subtle background and icon indicating "Make subtask" while swiping (e.g., an indent or arrow icon).
      • On swipe past threshold or on release, triggers the subtask conversion.
    • Avoid adding complex settings or extra buttons; keep it single-purpose.
  • Performance & safety:
    • Do not introduce any heavy new dependencies.
    • Follow existing state management patterns in the task list code.
  • Testing:
    • Add or update unit/widget tests that cover:
      • Swiping right on a middle-row task correctly makes it a subtask of the one above.
      • Swiping right on the first item is a no-op.
      • Behavior matches macOS indent logic (to the extent it’s testable in shared logic).

Implementation notes:

  • Focus on quickly locating the mobile task list widget and integrating into the existing architecture.
  • Don’t spend time installing/running the full app or wiring CI; just ensure code compiles locally in the dev environment and open a PR.
  • Keep changes minimal and tightly scoped to this feature.

Output:

  • Create a dedicated branch from main.
  • Implement the feature.
  • Add/update tests.
  • Open a PR titled: "feat(tasks): swipe right to create subtask on mobile" with a brief description of:
    • What changed
    • How to use it
    • How to test it manually on device/emulator.

AI Generated Changes

I'll implement the right-swipe to create subtask feature for the mobile Flutter app. Let me first examine the repository structure to understand the existing task management implementation.

Based on the repository structure, I need to locate the Flutter app files and understand the existing task management system. Let me implement the feature:

FILE: app/lib/pages/chat/widgets/task_list_item.dart

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.d...

---
*Generated by Claude AI via Omi*

… mirroring macOS Tab behavior.

Requirements:
- Platform: Flutter app in based-hardware/omi repo.
- Scope: Only mobile (iOS/Android) task list UI. Do NOT change macOS behavior, only reuse its logic.
- Behavior:
  - In the mobile task list (where tapping a task opens details and checkboxes toggle completion), add a right-swipe gesture on a task row.
  - Swiping right on a task should convert that task into a subtask of the task immediately above it in the list, following the same rules as macOS when pressing Tab on a task:
    - If there is a task above, current task becomes its child/subtask, one level deeper in the hierarchy.
    - If there is no task above (top of list), do nothing.
    - Maintain ordering semantics consistent with macOS implementation.
  - If macOS currently uses a shared function/util for "indent/make subtask", call that same function from the mobile swipe handler so logic stays consistent.
- UX details:
  - Use the same swipe pattern that already exists elsewhere in the app for destructive/non-destructive actions, if present (e.g., any existing Slidable/Dismissible conventions). If not, implement a simple, fast right-swipe interaction that:
    - Shows a subtle background and icon indicating "Make subtask" while swiping (e.g., an indent or arrow icon).
    - On swipe past threshold or on release, triggers the subtask conversion.
  - Avoid adding complex settings or extra buttons; keep it single-purpose.
- Performance & safety:
  - Do not introduce any heavy new dependencies.
  - Follow existing state management patterns in the task list code.
- Testing:
  - Add or update unit/widget tests that cover:
    - Swiping right on a middle-row task correctly makes it a subtask of the one above.
    - Swiping right on the first item is a no-op.
    - Behavior matches macOS indent logic (to the extent it’s testable in shared logic).

Implementation notes:
- Focus on quickly locating the mobile task list widget and integrating into the existing architecture.
- Don’t spend time installing/running the full app or wiring CI; just ensure code compiles locally in the dev environment and open a PR.
- Keep changes minimal and tightly scoped to this feature.

Output:
- Create a dedicated branch from main.
- Implement the feature.
- Add/update tests.
- Open a PR titled: "feat(tasks): swipe right to create subtask on mobile" with a brief description of:
  - What changed
  - How to use it
  - How to test it manually on device/emulator.


Generated by Claude AI via Omi
@kodjima33 kodjima33 merged commit d7d3bdb into main Jan 28, 2026
1 check passed
@kodjima33 kodjima33 deleted the claude-code-1769587970 branch January 28, 2026 08:16
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new feature to create subtasks by swiping on mobile. The implementation adds new models, providers, services, and UI components for task management. The code is well-structured, and it's great to see that tests have been included for the new UI interactions.

However, I've identified a few critical and high-severity issues that should be addressed:

  • There's an unsafe method for generating task IDs that could lead to duplicates.
  • The logic for sorting tasks hierarchically is flawed and will likely fail for nested subtasks.
  • Error handling in both the provider and service layers needs improvement to avoid swallowing exceptions and to provide better debugging information.
  • There are some unsafe type casts when deserializing data from JSON.
  • A minor style guide violation regarding color opacity was found, which violates a repository rule.

Please review the detailed comments for suggestions on how to resolve these issues.

onPressed: () {
if (titleController.text.trim().isNotEmpty) {
final newTask = Task(
id: DateTime.now().millisecondsSinceEpoch.toString(),
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

Using DateTime.now().millisecondsSinceEpoch.toString() to generate a task ID is not safe. It does not guarantee uniqueness, as two tasks could be created in the same millisecond, leading to duplicate IDs. This can cause unpredictable behavior, especially with list rendering and state management. A universally unique identifier (UUID) should be used instead.

You can use the uuid package to generate them. Add it to your pubspec.yaml and then use Uuid().v4().

Suggested change
id: DateTime.now().millisecondsSinceEpoch.toString(),
id: const Uuid().v4(),

Comment on lines +80 to +95
void _sortTasksByHierarchy() {
_tasks.sort((a, b) {
// First sort by parent hierarchy, then by original order
if (a.parentId == b.parentId) {
return (a.order ?? 0).compareTo(b.order ?? 0);
}

// If one is parent of another, parent comes first
if (a.id == b.parentId) return -1;
if (b.id == a.parentId) return 1;

// Otherwise maintain creation order
return (a.createdAt ?? DateTime.now())
.compareTo(b.createdAt ?? DateTime.now());
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The sorting logic in _sortTasksByHierarchy is not robust enough for a hierarchical structure. The pairwise comparison if (a.id == b.parentId) only handles direct parent-child relationships and will fail to correctly order tasks with multiple levels of nesting. This can result in an incorrectly ordered task list.

A more reliable approach would be to first build a tree structure from the flat list of tasks, and then perform a depth-first traversal to generate the correctly ordered list for display. Additionally, using DateTime.now() as a fallback for null createdAt values can cause non-deterministic sorting.

parentId: json['parentId'],
depth: json['depth'],
order: json['order'],
subtaskIds: json['subtaskIds']?.cast<String>(),
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The use of cast<String>() is not type-safe. If the subtaskIds list from the API contains any non-string values, this will lead to a runtime error when the list is first accessed, which can be difficult to debug. It's safer to create a new list and validate the types eagerly.

Suggested change
subtaskIds: json['subtaskIds']?.cast<String>(),
subtaskIds: json['subtaskIds'] != null ? List<String>.from(json['subtaskIds']) : null,

color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).dividerColor.withOpacity(0.2),
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The repository's general rules specify using the custom withValues({double? alpha}) extension method instead of the deprecated withOpacity(). This rule is violated here and in other places in this file.

Suggested change
color: Theme.of(context).dividerColor.withOpacity(0.2),
color: Theme.of(context).dividerColor.withValues(alpha: 0.2),
References
  1. Use the custom withValues({double? alpha}) extension method instead of the deprecated withOpacity() to modify a color's opacity.

Comment on lines +27 to +38
Future<void> toggleTaskComplete(String taskId, bool completed) async {
try {
await _taskService.updateTaskCompletion(taskId, completed);

final taskIndex = _tasks.indexWhere((task) => task.id == taskId);
if (taskIndex != -1) {
_tasks[taskIndex] = _tasks[taskIndex].copyWith(completed: completed);
notifyListeners();
}
} catch (e) {
debugPrint('Error toggling task completion: $e');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Errors from asynchronous operations like toggleTaskComplete are caught but only printed to the debug console. This means that if an operation fails (e.g., due to a network error), the user receives no feedback. The exception should be re-thrown so it can be caught by the UI layer and an appropriate error message can be displayed to the user.

Suggested change
Future<void> toggleTaskComplete(String taskId, bool completed) async {
try {
await _taskService.updateTaskCompletion(taskId, completed);
final taskIndex = _tasks.indexWhere((task) => task.id == taskId);
if (taskIndex != -1) {
_tasks[taskIndex] = _tasks[taskIndex].copyWith(completed: completed);
notifyListeners();
}
} catch (e) {
debugPrint('Error toggling task completion: $e');
}
Future<void> toggleTaskComplete(String taskId, bool completed) async {
try {
await _taskService.updateTaskCompletion(taskId, completed);
final taskIndex = _tasks.indexWhere((task) => task.id == taskId);
if (taskIndex != -1) {
_tasks[taskIndex] = _tasks[taskIndex].copyWith(completed: completed);
notifyListeners();
}
} catch (e) {
debugPrint('Error toggling task completion: $e');
rethrow;
}
}

Comment on lines +11 to +29
Future<List<Task>> getAllTasks() async {
try {
final response = await http
.get(
Uri.parse('$_baseUrl/tasks'),
headers: {'Content-Type': 'application/json'},
)
.timeout(_timeout);

if (response.statusCode == 200) {
final List<dynamic> jsonList = json.decode(response.body);
return jsonList.map((json) => Task.fromJson(json)).toList();
} else {
throw Exception('Failed to load tasks: ${response.statusCode}');
}
} catch (e) {
throw Exception('Failed to load tasks: $e');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The error handling in this service uses a broad try-catch that wraps any exception in a new generic Exception. This practice masks the original error type and stack trace, making it significantly harder to debug issues. A network error, JSON parsing error, or data validation error will all appear as a generic 'Failed to load tasks' exception. It's better to rethrow the original exception to preserve these crucial details.

  Future<List<Task>> getAllTasks() async {
    try {
      final response = await http
          .get(
            Uri.parse('$_baseUrl/tasks'),
            headers: {'Content-Type': 'application/json'},
          )
          .timeout(_timeout);

      if (response.statusCode == 200) {
        final List<dynamic> jsonList = json.decode(response.body);
        return jsonList.map((json) => Task.fromJson(json)).toList();
      } else {
        throw Exception('Failed to load tasks: ${response.statusCode}');
      }
    } catch (e) {
      // Rethrowing the original exception preserves the stack trace and error type,
      // which is crucial for debugging.
      rethrow;
    }
  }

kodjima33 added a commit that referenced this pull request Jan 28, 2026
…subtask, mirroring macOS Tab behavior. (#4423)"

This reverts commit d7d3bdb.
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