Skip to content

Allow editing Kanban card properties inline - #9102

Open
elias-marrero-gm wants to merge 2 commits into
AppFlowy-IO:mainfrom
elias-marrero-gm:feat/kanban-inline-property-editing
Open

elias-marrero-gm wants to merge 2 commits into
AppFlowy-IO:mainfrom
elias-marrero-gm:feat/kanban-inline-property-editing

Conversation

@elias-marrero-gm

@elias-marrero-gm elias-marrero-gm commented Oct 27, 2026 •

Copy link
Copy Markdown

Feature Preview

Adds inline property editing to Kanban cards, allowing users to update properties without opening the card.

  • Reading mode shows filled properties; editing mode exposes all properties.
  • Clicking Done or outside the card saves changes and finishes editing.
  • With the date picker open, the first outside click saves the date and closes the picker; a second outside click finishes card editing.
  • Tab and Shift+Tab navigate between editable fields.
  • Clicking the title during editing edits it without opening the card.
  • Property clicks interact with their controls without opening the card.
  • A hover-only expand button appears during editing to save changes and open the card.
AppFlowy.1.mp4

Validation

  • All 35 Kanban feature tests passed together.
  • Two grid tests and the repository warm-up also passed: 38 Windows integration tests total.
  • Both popover keyboard-focus tests passed.
  • Dart analysis passed for the changed files.
  • Windows debug build succeeded.

The full suite is not passing locally:

  • Eight remaining desktop test failures also occurred on this branch’s base revision, which were there before this feature.
  • One Rust test fails because an unchanged fixture has Windows line endings.
  • Cloud-dependent and mobile suites were not run.

PR Checklist

  • My code adheres to AppFlowy's Conventions.
  • I've listed at least one issue that this PR fixes in the description above. No linked enhancement issue yet.
    (I didn't fix an issue, I added some functionalities)
  • I've added tests to validate changes in this PR.
  • All existing tests are passing. See validation results above.
    (Referenced in Validation)

Summary by Sourcery

Add inline Kanban card property editing with reliable saving, keyboard navigation, and card-opening behavior.

New Features:

  • Enable inline editing of Kanban card properties, including titles, supported field types, checkboxes, and grouping values.
  • Expose empty or hidden properties during editing and provide controls to save changes or open the full card.

Bug Fixes:

  • Preserve edits when focus changes, popovers close, cards are opened, or editing switches between cards.

Enhancements:

  • Add keyboard navigation across editable card fields and popover editors with Tab and Shift+Tab.
  • Improve card interaction handling so property controls do not unintentionally open cards while preserving selection, dragging, and normal reading-mode behavior.
  • Connect property popover focus to its owning card control and support editable placeholders for empty values.

Tests:

  • Add comprehensive desktop integration coverage for Kanban property editing, saving, focus navigation, popovers, visibility, and card interactions.
  • Add popover keyboard-focus tests.

@sourcery-ai

sourcery-ai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces desktop Kanban card inline property editing by adding an explicit editing session, reusing existing cell popovers, managing save/focus/outside-click behavior, and covering the feature with extensive Windows integration tests.

Sequence diagram for inline Kanban card property editing

sequenceDiagram
    actor User
    participant Card as RowCard
    participant Bloc as CardBloc
    participant Cell as BoardCardCell
    participant Editor as EditableCellWidget
    participant Popover as PropertyPopover

    User->>Card: onStartEditing()
    Card->>Bloc: setIsEditing(true)
    Bloc-->>Card: showAllFieldsWhenEditing
    User->>Cell: Tap property
    Cell->>Editor: buildStyled()
    Editor->>Popover: Open property editor
    User->>Editor: Change value
    User->>Card: Tap outside or DoneCardButton
    Card->>Editor: unfocus()
    Card->>Bloc: setIsEditing(false)
    Bloc-->>Card: Restore visible fields
Loading

Flow diagram for Kanban card editing and focus navigation

flowchart TD
    A[Reading mode: visible properties] -->|Edit card| B[Editing mode: all properties]
    B --> C[Click property control]
    C --> D[Open existing cell editor or popover]
    B --> E[Tab or Shift+Tab]
    E --> F[Commit current field and move focus]
    D --> G{Outside click}
    G -->|Property popover open| H[Close popover and keep card editing]
    G -->|No popover| I[Unfocus and finish editing]
    B --> J[Click title]
    J --> K[Edit title without opening card]
    B --> L[OpenCardAccessory]
    L --> I
Loading

File-Level Changes

Change Details Files
Adds a board-card editing session that exposes editable and hidden properties inline while preserving compact reading mode.
  • Introduces an opt-in property-editing path for desktop board cards.
  • Rebuilds visible card fields when editing starts or ends, including hidden and grouping fields.
  • Adds placeholders for empty properties during editing and preserves normal visibility afterward.
  • Cleans up active editing sessions when cards are disposed or another card becomes active.
frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_builder.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/checklist_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/date_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/media_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/number_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/relation_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/select_option_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/time_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/url_card_cell.dart
Routes board-card property interactions through inline editors without triggering row-detail navigation.
  • Adds a BoardCardCell wrapper that opens the existing cell editors for supported field types.
  • Keeps property clicks, checkbox actions, grouping changes, and drag/selection gestures within the card.
  • Allows title editing during an active card session while retaining title-to-open behavior in reading mode.
  • Adds a hover-only Done action and an expand action that saves edits before opening the full card.
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/board_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/accessory.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/checkbox_card_cell.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart
Implements keyboard focus management and outside-click save semantics for inline editing.
  • Adds stable per-field focus nodes with Tab and Shift+Tab traversal across editable fields and Done.
  • Commits the current field and closes popovers when focus changes.
  • Uses TapRegion and focus flushing to save on Done, outside clicks, or card opening.
  • Handles date-picker dismissal as an intermediate outside-click step before ending the card session.
frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart
frontend/appflowy_flutter/lib/plugins/database/widgets/cell/board_card_cell.dart
frontend/appflowy_flutter/packages/appflowy_popover/lib/src/popover.dart
Adds broad integration coverage for inline editing behavior and registers it in the Windows board test runner.
  • Covers property types, title editing, empty-field visibility, persistence, popovers, date handling, navigation, focus traversal, selection, dragging, and multi-card sessions.
  • Adds popover focus-connection tests and stabilizes board setup by waiting for the getting-started page.
frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart
frontend/appflowy_flutter/integration_test/desktop/board/board_row_test.dart
frontend/appflowy_flutter/integration_test/desktop/board/board_test_runner.dart
frontend/appflowy_flutter/packages/appflowy_popover/test/popover_test.dart

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

@CLAassistant

CLAassistant commented Oct 27, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart" line_range="624-628" />
<code_context>
+    if (_isEditing) {
+      final String groupId = widget.afGroupData.id;
+      final String rowId = widget.groupItem.id;
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        if (!_boardActions.isClosed) {
+          _boardActions
+              .endEditing(GroupedRowId(groupId: groupId, rowId: rowId));
+        }
+      });
+    }
</code_context>
<issue_to_address>
**Disposed card ends newer edit**

When an edited card is disposed and another card starts editing before the deferred post-frame callback runs, the deferred callback calls `endEditing` without checking which row is currently being edited; `BoardActionsCubit.endEditing` emits an unscoped end-editing event, so every card listener ends its session, including a different card’s newly started session.

Before ending the session, verify that the disposed card is still the active edit target, or make the end-editing event target-aware and ignore it in other cards.
</issue_to_address>

### Comment 2
<location path="frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart" line_range="198" />
<code_context>
+              // particular, closing the date picker keeps its saved selection
+              // and leaves the card open for further editing.
+              if (PopoverState.rootEntry.isEmpty) {
+                _finishEditing();
+              }
+            },
</code_context>
<issue_to_address>
**Unrelated popovers keep cards editing**

When an unrelated popover is open as the user clicks outside an editing card, `PopoverState.rootEntry` tracks popovers globally, so the condition skips `_finishEditing()` and the card remains in edit mode after the user clicks away.

Check whether a popover belonging to this card is open instead of checking the global popover stack.
</issue_to_address>

### Comment 3
<location path="frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart" line_range="964" />
<code_context>
+
+      final DateCellDataPB data =
+          DateCellDataPB.fromBuffer((await _getCell(card, field)).data);
+      expect(data.hasTimestamp(), isTrue);
+      expect(find.byType(RowDetailPage), findsNothing);
+
</code_context>
<issue_to_address>
**Wrong dates still pass**

When the date editor persists a timestamp for a different day than the one selected, when the date editor persists a timestamp for a different day than the one selected, the `set and clear a date from the board` test at line 964 and the corresponding checks at lines 576 and 965 only require `hasTimestamp()` to be true, so the tests pass while the saved date is wrong.

Assert that the persisted date matches the day selected in each test.

Also at `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:575`, `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:770`.
</issue_to_address>

### Comment 4
<location path="frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart" line_range="968-970" />
<code_context>
+      expect(find.byType(RowDetailPage), findsNothing);
+
+      await tester.tapButton(property);
+      await tester.clearDate();
+      await tester.dismissCellEditor();
+      await tester.pumpUntilFound(
+        find.descendant(of: property, matching: find.text(field.name)),
+      );
</code_context>
<issue_to_address>
**Clearing date is not verified**

When the clear action updates the editor's local display but leaves the saved cell unchanged, when the clear action updates only the editor's local display but leaves the saved cell unchanged, the `set and clear a date from the board` test at lines 967-972 only waits for the field-name placeholder, so it passes with the old date still persisted.

Read the cell after clearing and assert that its timestamp is absent.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +624 to +628
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_boardActions.isClosed) {
_boardActions
.endEditing(GroupedRowId(groupId: groupId, rowId: rowId));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium · Disposed card ends newer edit

When an edited card is disposed and another card starts editing before the deferred post-frame callback runs, the deferred callback calls endEditing without checking which row is currently being edited; BoardActionsCubit.endEditing emits an unscoped end-editing event, so every card listener ends its session, including a different card’s newly started session.

Before ending the session, verify that the disposed card is still the active edit target, or make the end-editing event target-aware and ignore it in other cards.

Prompt for AI agents
In `frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart` at lines 624-628:

**Disposed card ends newer edit**

When an edited card is disposed and another card starts editing before the deferred post-frame callback runs, the deferred callback calls `endEditing` without checking which row is currently being edited; `BoardActionsCubit.endEditing` emits an unscoped end-editing event, so every card listener ends its session, including a different card’s newly started session.

Before ending the session, verify that the disposed card is still the active edit target, or make the end-editing event target-aware and ignore it in other cards.

// particular, closing the date picker keeps its saved selection
// and leaves the card open for further editing.
if (PopoverState.rootEntry.isEmpty) {
_finishEditing();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium · Unrelated popovers keep cards editing

When an unrelated popover is open as the user clicks outside an editing card, PopoverState.rootEntry tracks popovers globally, so the condition skips _finishEditing() and the card remains in edit mode after the user clicks away.

Check whether a popover belonging to this card is open instead of checking the global popover stack.

Prompt for AI agents
In `frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart` at line 198:

**Unrelated popovers keep cards editing**

When an unrelated popover is open as the user clicks outside an editing card, `PopoverState.rootEntry` tracks popovers globally, so the condition skips `_finishEditing()` and the card remains in edit mode after the user clicks away.

Check whether a popover belonging to this card is open instead of checking the global popover stack.


final DateCellDataPB data =
DateCellDataPB.fromBuffer((await _getCell(card, field)).data);
expect(data.hasTimestamp(), isTrue);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium · Wrong dates still pass

When the date editor persists a timestamp for a different day than the one selected, when the date editor persists a timestamp for a different day than the one selected, the set and clear a date from the board test at line 964 and the corresponding checks at lines 576 and 965 only require hasTimestamp() to be true, so the tests pass while the saved date is wrong.

Assert that the persisted date matches the day selected in each test.

Also at frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:575, frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:770.

Prompt for AI agents
In `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart` at line 964:

**Wrong dates still pass**

When the date editor persists a timestamp for a different day than the one selected, when the date editor persists a timestamp for a different day than the one selected, the `set and clear a date from the board` test at line 964 and the corresponding checks at lines 576 and 965 only require `hasTimestamp()` to be true, so the tests pass while the saved date is wrong.

Assert that the persisted date matches the day selected in each test.

Also at `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:575`, `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart:770`.

Comment on lines +968 to +970
await tester.clearDate();
await tester.dismissCellEditor();
await tester.pumpUntilFound(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium · Clearing date is not verified

When the clear action updates the editor's local display but leaves the saved cell unchanged, when the clear action updates only the editor's local display but leaves the saved cell unchanged, the set and clear a date from the board test at lines 967-972 only waits for the field-name placeholder, so it passes with the old date still persisted.

Read the cell after clearing and assert that its timestamp is absent.

Prompt for AI agents
In `frontend/appflowy_flutter/integration_test/desktop/board/board_property_edit_test.dart` at lines 968-970:

**Clearing date is not verified**

When the clear action updates the editor's local display but leaves the saved cell unchanged, when the clear action updates only the editor's local display but leaves the saved cell unchanged, the `set and clear a date from the board` test at lines 967-972 only waits for the field-name placeholder, so it passes with the old date still persisted.

Read the cell after clearing and assert that its timestamp is absent.

This branch has not been deployed

No deployments
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