Skip to content

Conversation

@Chithrakc99
Copy link

@Chithrakc99 Chithrakc99 commented Dec 16, 2025

This PR adds initial UI groundwork for a tag section in the workspace.
The feature is not wired to filtering logic yet and is intended as a
foundation for future tag-based search and filtering.

I’m opening this early to get feedback on placement and approach
before implementing full behavior.

Summary by Sourcery

Introduce initial workspace tag UI components and basic tag model to prepare for future tag-based search and filtering.

New Features:

  • Add a tag search bar to the workspace home view for entering tag queries.
  • Introduce reusable Tag and TagFilter application-layer classes to represent and match tags.
  • Add a TagEditor widget for creating, displaying, and removing tags in the UI.

Enhancements:

  • Wire the new tag search bar into the home stack with a temporary onChanged handler for debugging.

@CLAassistant
Copy link

CLAassistant commented Dec 16, 2025

CLA assistant check
All committers have signed the CLA.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 16, 2025

Reviewer's Guide

Introduces initial, UI-only tag support in the workspace home view by adding a tag search bar, a basic tag editor widget, and a simple Tag model plus filtering helper, without yet wiring them into actual workspace filtering logic.

Sequence diagram for tag search bar interaction in HomeStack

sequenceDiagram
  actor User
  participant HomeStackState
  participant TagSearchBar

  User->>HomeStackState: Open workspace home
  HomeStackState->>TagSearchBar: Build TagSearchBar(onChanged)
  User->>TagSearchBar: Type characters in search field
  TagSearchBar-->>HomeStackState: onChanged(query)
  HomeStackState->>HomeStackState: debugPrint(Search query: query)
  HomeStackState-->>User: No filtering applied yet
Loading

Class diagram for new tag-related UI and model

classDiagram
  class Tag {
    +String id
    +String name
    +int color
    +Tag(String id, String name, int color)
  }

  class TagFilter {
    +bool match(List~Tag~ tags, String query)
  }

  class TagEditor {
    +List~Tag~ tags
    +void onChanged(List~Tag~ tags)
    +TagEditor(List~Tag~ tags, void onChanged(List~Tag~ tags))
  }

  class _TagEditorState {
    -TextEditingController controller
    +void _addTag(String value)
    +Widget build(BuildContext context)
  }

  class TagSearchBar {
    +void onChanged(String query)
    +TagSearchBar(void onChanged(String query))
    +Widget build(BuildContext context)
  }

  class HomeStackState {
    +int selectedIndex
    +Widget build(BuildContext context)
  }

  TagFilter ..> Tag : uses
  TagEditor --> Tag : manages
  TagEditor <|-- _TagEditorState : state
  TagSearchBar <-- HomeStackState : contains
  TagEditor ..> HomeStackState : future_integration
Loading

Flow diagram for TagFilter.match query evaluation

flowchart TD
  A_start["Start TagFilter.match"] --> B_check_empty["Is query empty?"]
  B_check_empty -->|Yes| C_return_true["Return true (no filtering)"]
  B_check_empty -->|No| D_lower_query["Convert query to lowercase"]
  D_lower_query --> E_any_match["Does any tag.name contain query?"]
  E_any_match -->|Yes| F_return_true["Return true (tags match query)"]
  E_any_match -->|No| G_return_false["Return false (no tags match)"]
  C_return_true --> H_end["End"]
  F_return_true --> H_end
  G_return_false --> H_end
Loading

File-Level Changes

Change Details Files
Add a tag search bar to the workspace home stack as a placeholder for future tag-based filtering.
  • Import the TagSearchBar widget into the home stack file
  • Render TagSearchBar above the main content area, wrapped in horizontal padding
  • Temporarily handle search input by logging the query with debugPrint instead of applying real filters
frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart
frontend/appflowy_flutter/lib/workspace/presentation/widgets/tags/tag_search_bar.dart
Introduce a reusable tag editor widget for adding and removing tags via chips and a text field.
  • Create a TagEditor stateful widget that accepts a list of Tag objects and an onChanged callback
  • Render existing tags as deletable Chip widgets
  • Provide a TextField that creates a new Tag with a generated id and default blue color when submitted, then notifies via onChanged
frontend/appflowy_flutter/lib/workspace/presentation/widgets/tags/tag_editor.dart
Define foundational tag domain types and a helper for evaluating tag matches against a free-text query.
  • Add a simple Tag model with id, name, and color fields
  • Implement TagFilter.match to return true when the query is empty or when any tag name contains the query case-insensitively
frontend/appflowy_flutter/lib/workspace/application/tag/tag.dart
frontend/appflowy_flutter/lib/workspace/application/tag/tag_filter.dart
Add editor/IDE metadata files (likely local tooling artifacts) to the repository.
  • Introduce Visual Studio document layout and workspace state JSON files under .vs/
.vs/AppFlowy/v17/DocumentLayout.json
.vs/VSWorkspaceState.json

Possibly linked issues

  • #FR (no number provided): PR starts the requested tag-based organization by adding tag UI, model, and basic search/filter helper utilities.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In _HomeStackState, the temporary debugPrint('Search query: $query'); should either be gated behind an assert/debug flag or replaced with a TODO so it doesn’t accidentally ship in production.
  • TagEditor creates a TextEditingController but does not dispose it; override dispose() in _TagEditorState and call controller.dispose() to avoid potential memory leaks.
  • The .vs/AppFlowy/v17/DocumentLayout.json and .vs/VSWorkspaceState.json files look like local IDE artifacts and should generally be excluded from version control.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_HomeStackState`, the temporary `debugPrint('Search query: $query');` should either be gated behind an assert/debug flag or replaced with a TODO so it doesn’t accidentally ship in production.
- `TagEditor` creates a `TextEditingController` but does not dispose it; override `dispose()` in `_TagEditorState` and call `controller.dispose()` to avoid potential memory leaks.
- The `.vs/AppFlowy/v17/DocumentLayout.json` and `.vs/VSWorkspaceState.json` files look like local IDE artifacts and should generally be excluded from version control.

## Individual Comments

### Comment 1
<location> `frontend/appflowy_flutter/lib/workspace/presentation/widgets/tags/tag_editor.dart:18-19` </location>
<code_context>
+  State<TagEditor> createState() => _TagEditorState();
+}
+
+class _TagEditorState extends State<TagEditor> {
+  final controller = TextEditingController();
+
+  void _addTag(String value) {
</code_context>

<issue_to_address>
**issue (bug_risk):** TextEditingController should be disposed to avoid leaks.

Because `_TagEditorState` creates the `TextEditingController`, override `dispose` and dispose the controller before calling `super.dispose()`:

```dart
@override
void dispose() {
  controller.dispose();
  super.dispose();
}
```
</issue_to_address>

### Comment 2
<location> `frontend/appflowy_flutter/lib/workspace/application/tag/tag_filter.dart:4-11` </location>
<code_context>
+import 'tag.dart';
+
+class TagFilter {
+  static bool match({
+    required List<Tag> tags,
+    required String query,
+  }) {
+    if (query.isEmpty) return true;
+
+    final lower = query.toLowerCase();
+    return tags.any((tag) => tag.name.toLowerCase().contains(lower));
+  }
+}
</code_context>

<issue_to_address>
**suggestion:** Trimming and normalizing the query would make matching more robust.

Because the emptiness check runs before lowercasing, a query like `'  '` is treated as non-empty and never matches anything. Trim and lowercase once (e.g. `final normalized = query.trim().toLowerCase();`) and use `normalized.isEmpty` and `normalized` for matching to handle whitespace-only queries correctly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +4 to +11
static bool match({
required List<Tag> tags,
required String query,
}) {
if (query.isEmpty) return true;

final lower = query.toLowerCase();
return tags.any((tag) => tag.name.toLowerCase().contains(lower));
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Trimming and normalizing the query would make matching more robust.

Because the emptiness check runs before lowercasing, a query like ' ' is treated as non-empty and never matches anything. Trim and lowercase once (e.g. final normalized = query.trim().toLowerCase();) and use normalized.isEmpty and normalized for matching to handle whitespace-only queries correctly.

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