Skip to content

Route the checklist's first item through the popover in popover style#7797

Open
azooz2003-bit wants to merge 7 commits into
restore-workspace-todos-default-nonefrom
checklist-popover-first-item
Open

Route the checklist's first item through the popover in popover style#7797
azooz2003-bit wants to merge 7 commits into
restore-workspace-todos-default-nonefrom
checklist-popover-first-item

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Stacks on #7790

This PR is stacked on #7790 (restore-workspace-todos-default-none) and should be reviewed/merged after it. Do not merge this PR on its own.

Summary

SidebarWorkspaceChecklistView.swift's presentsPopover was usesPopoverPresentation && totalCount > 0, so a workspace with zero checklist items always fell into inline text-field entry regardless of sidebar.beta.workspaceTodos.checklistStyle (default .popover). A workspace's very first checklist item never got the popover treatment.

  • presentsPopover now equals usesPopoverPresentation directly (no totalCount gate).
  • Moved the ChecklistSummaryPopoverModifier (the NSPopover anchor) from summaryLine to the section's outer container, so the same backing NSView/Coordinator anchors the popover across the 0->1 item transition instead of re-anchoring to a freshly created view (which would close and immediately reopen the popover).
  • The ghost "Add item" row's button now routes through the same popover-toggle path as the summary line when presentsPopover is true.
  • Fixed a second bug in ContentView.swift's .onReceive handler for .workspaceChecklistAddItemRequested: it special-cased !workspace.todoState.checklist.isEmpty before routing to the popover, so the "Add Checklist Item..." context-menu/palette action still fell back to inline for a workspace's first item even after the view-level fix. Removed that gate; popover style now always routes the add request into the popover.
  • .inline style is untouched: presentsPopover is false there regardless of item count, same as before.

No changes to WorkspaceTodoChecklistStyle, its default, the settings UI, or WorkspaceTodoState.statusHidden/default-status logic (all handled by #7790).

Test plan

No new pure-logic seam was created worth a unit test: presentsPopover reduces to exactly usesPopoverPresentation, an identity, and the rest of the fix is SwiftUI view-wiring (anchor attachment point, button action branch, notification-handler condition) that isn't expressible as a standalone testable function. Verified manually instead, tag chkpop:

  • Fresh workspace, zero items, .popover style (default): triggering "Add Checklist Item..." (context-menu/palette path, exercised via the debug.workspace_todo.checklist_add_field debug RPC, which shares the same WorkspaceTodoActions.requestChecklistAddField path as the context menu and the ghost row's own tap) opens the anchored NSPopover immediately, with the header showing the workspace title and 0/0, and the add field focused — no intermediate inline TextField state at any point. Screenshot evidence captured.
  • Typed into the popover's add field and committed: item appears (1. [ ] Buy milk, 0/1 completed), popover stays anchored (no visible close/reopen), summary line updates to 0/1 · Buy milk.
  • Reopened the popover on the now-non-empty checklist (regression check) and added a second item (2. [ ] Walk the dog, 0/2 completed) — pre-existing popover-for-non-empty-checklist behavior unaffected.
  • Fresh second workspace with sidebarWorkspaceTodosChecklistStyle overridden to inline: triggering the same add-item path shows the inline ghost-row TextField (not a popover) for the very first item — confirms no regression to the non-default path.
  • Build: ./scripts/reload.sh --tag chkpop succeeded (local reload; reload-cloud.sh known-broken in this hq checkout).
  • Killed the tagged app after verification; no stray cmux DEV chkpop processes remain.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches NSPopover lifecycle, focus, and sidebar row layout/hover in a user-facing area; changes are localized to workspace todo UI with no data or auth impact.

Overview
Popover style now covers empty checklists end-to-end. presentsPopover no longer requires items; add-item notifications and context-menu paths always open the anchored NSPopover in .popover mode. The popover anchor moves from the summary line to the section container (1×1pt overlay) so it stays stable across 0→1 items, and SidebarWorkspaceTodoPopoverHost retries present() when the anchor isn’t in a window yet instead of clearing isPresented.

Sidebar checklist visibility and layout. The checklist block can show when there’s content or a pending add even if workspace status is hidden. Inline and popover lists drop the “… N more” clamp in favor of a 6-row scroll viewport; item text wraps with checkbox/delete aligned to the first line. Rows get hover-reveal delete (plus tooltip sidebar.checklist.removeItemTooltip); the popover add field gains Backspace-to-delete highlighted items and stricter highlight vs. draft keyboard rules.

Todo pane gets the same multiline wrapping / first-line checkbox alignment for long items.

Reviewed by Cursor Bugbot for commit 7996c98. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Empty checklists in .popover style now open the checklist popover for the first item reliably, with a stable anchor, smooth focus, and clear keyboard behavior. .inline is unchanged; the checklist section renders when status is hidden, and long item text now wraps with aligned controls in the Todo Pane.

  • Bug Fixes

    • Always present the popover in .popover (no totalCount gate).
    • Move the popover anchor to the section container as a 1×1pt .overlay; add an AnchorView that retries present() on window attach for zero-item workspaces; fixes open reliability and hover interactions.
    • Always route .workspaceChecklistAddItemRequested to the popover in .popover.
    • Remove the inline ghost “Add item” row when empty in .popover; open the popover instead.
    • Render the checklist section when there’s content or an add request, even if status is hidden.
    • Use a scrollable list capped at 6 rows in both inline and popover; remove the “… N more” clamp.
    • Align checkbox/remove to the first line of wrapped text. Clear highlight while typing; ignore highlight changes during a live draft.
    • Fix text wrap and first-line alignment in the Todo Pane checklist.
  • New Features

    • Hover-reveal delete button on checklist rows (inline and popover) with tooltip sidebar.checklist.removeItemTooltip (en, ja).
    • Backspace deletes the highlighted item when the add field is empty.

Written for commit 7996c98. Summary will update on new commits.

Review in cubic

usesPopoverPresentation && totalCount > 0 forced inline entry for a
workspace's very first checklist item regardless of the popover setting,
since an empty checklist has no summary line to anchor a popover to.
Move the popover anchor from the summary line to the section container
(stable across the 0->1 item transition), route the ghost "Add item"
button through the same popover-toggle path as the summary line, and fix
the "Add Checklist Item..." notification handler in ContentView.swift
(it special-cased non-empty checklists, so the context-menu/palette path
still fell back to inline for a fresh workspace even after the view-level
fix). Inline style is unaffected.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6706f9d0-73e2-496b-a4bf-c8a6f2c7cce3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch checklist-popover-first-item

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

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 11, 2026 1:57am
cmux-staging Building Building Preview, Comment Jul 11, 2026 1:57am

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates workspace checklist popover behavior for first-item creation. The main changes are:

  • Routes popover-style checklist add requests through the popover even when the checklist is empty.
  • Moves the checklist popover host to a stable section-level AppKit anchor.
  • Adds retry behavior for presenting after the anchor attaches to a window.
  • Updates checklist rows to wrap, scroll after six items, and show a localized hover remove button.

Confidence Score: 4/5

This is close, but the popover dismissal state should be fixed before merging.

  • Empty popover-style checklist add requests still leave the add token pending when the popover closes without a commit.
  • The stale token can keep an empty checklist section rendered after the add flow is dismissed.
  • The add-row close behavior from the changed sidebar layout appears addressed.

Sources/ContentView.swift and the checklist popover close callbacks.

Important Files Changed

Filename Overview
Sources/ContentView.swift Routes checklist add requests to the popover path and keeps empty sections visible while an add token is pending.
Sources/SidebarWorkspaceChecklistView.swift Moves the checklist popover host to the section container and hides the inline add row in popover mode.
Sources/SidebarWorkspaceChecklistPopover.swift Adds wrapping checklist rows, a capped scroll area, keyboard deletion, and the hover remove affordance.
Sources/SidebarWorkspaceTodoPopoverHost.swift Adds a window-attachment retry for AppKit popover presentation.
Resources/Localizable.xcstrings Adds English and Japanese strings for the new remove-item tooltip.

Comments Outside Diff (1)

  1. Sources/ContentView.swift, line 10558 (link)

    P1 Activation token remains pending This path increments checklistAddFieldActivationTokens before opening the popover, but dismissing the popover without adding an item does not clear that token. In an empty checklist using popover style, a user can trigger “Add Checklist Item…”, then click outside the transient popover or press Escape. The close path clears the presented popover, but the positive token still satisfies the sidebar section visibility gate, so the empty checklist section can stay alive only because of stale add state. Clear the token when the popover add flow closes without a commit, or make section visibility stop depending on the token after dismissal.

Reviews (7): Last reviewed commit: "Fix checklist item text wrap in the dedi..." | Re-trigger Greptile

/// below, which is present whether or not a summary line exists yet.
private var presentsPopover: Bool {
usesPopoverPresentation && totalCount > 0
usesPopoverPresentation

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.

P1 Activation Token Stays Pending

When an empty checklist is opened through the new popover path and then dismissed without adding an item, the inline cancel path no longer runs and the popover close path does not consume addFieldActivationToken. That leaves checklistAddFieldActivationTokens[workspaceId] > 0, so an otherwise-empty checklist section can stay rendered indefinitely just because of stale add state.

Rule Used: Flag SwiftUI changes that can cause stale state, b... (source)

Comment on lines +328 to +330
if presentsPopover {
onPopoverPresentedChange(!isPopoverPresented)
} else {

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.

P2 Add Row Closes Popover

For an empty checklist opened by the add-item notification, the ghost “Add item” row remains visible while the popover is already open. Clicking it sends false here and closes the popover instead of keeping the add field available, so the add affordance behaves like a dismiss action.

Suggested change
if presentsPopover {
onPopoverPresentedChange(!isPopoverPresented)
} else {
if presentsPopover {
onPopoverPresentedChange(true)
} else {

Rule Used: Flag SwiftUI changes that can cause stale state, b... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

taskStatus is nil whenever a workspace's status is hidden (statusHidden),
which restore-workspace-todos-default-none made the default for all new
workspaces. The checklist section's render gate required taskStatus != nil,
so "Add Checklist Item..." silently did nothing for any new workspace.
Drop that condition; the checklist section only needs its own content/token
gate.
Comment on lines 327 to +332
Button {
isAddingItem = true
if presentsPopover {
onPopoverPresentedChange(!isPopoverPresented)
} else {
isAddingItem = true
}

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.

P1 Add Row Dismisses In an empty checklist, expandedList keeps this ghost add row visible while the popover is already open. Clicking “Add item” then sends false through this toggle and closes the popover instead of keeping the add field available. The row should open the popover when closed and avoid dismissing it when it is already presented.

Comment thread Sources/ContentView.swift
The only way to delete a checklist item was a hidden right-click
"Remove" context menu entry. Add a trailing xmark.circle.fill button
that appears on row hover (reserved layout space, opacity/hit-test
toggle only, so row height never jumps) in both row implementations:
the inline sidebar row and the NSPopover-hosted row. Both call the
existing actions.removeItem(_:) closure, the same one the context
menu already used; the context menu entry is unchanged.

New localized tooltip key sidebar.checklist.removeItemTooltip
("Remove item" / "項目を削除").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… add keyboard delete

- Highlighting a checklist item (click, Up/Down) and typing a new item
  in the add field are now mutually exclusive: a non-empty draft clears
  any highlight, and highlight-setting paths no-op while a draft exists.
  Makes Return's outcome (toggle item vs. commit new item) unambiguous.
- Popover-style checklists no longer show a persistent ghost "Add item"
  row inline when a workspace has zero items. "Add Checklist Item…" now
  opens the popover directly with nothing shown inline until an item is
  actually added, at which point the existing summary line is the small
  status preview.
- Item rows now center-align the checkbox and text (was firstTextBaseline,
  which misaligned the checkbox glyph against the text's baseline).
- Backspace with an empty draft and a highlighted item now removes it, a
  keyboard-driven delete alongside the existing hover "x" and context
  menu "Remove".
Comment thread Sources/ContentView.swift
// checklist) — independent of whether a status glyph is shown,
// since workspaces now default to hidden status and must still
// be able to add checklist items from that state.
if !workspaceSnapshot.checklistItems.isEmpty || checklistAddFieldActivationToken > 0 {

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.

P1 Activation Token Persists When an empty checklist is opened through the popover and then dismissed without adding an item, the add-field activation token is still left in checklistAddFieldActivationTokens. The popover close path clears checklistPopoverWorkspaceId, but it does not consume the token. Since this gate keeps rendering the checklist section whenever checklistAddFieldActivationToken > 0, an otherwise-empty workspace can keep an empty checklist section alive after the user clicks outside the transient popover or cancels without committing. Clear the token when the popover add flow closes without a commit, or stop using the pending activation token as section visibility state after dismissal.

Comment thread Sources/ContentView.swift
An empty VStack collapses to a degenerate frame, which broke the
NSPopover anchor view's bounds/window attachment when a workspace had
zero checklist items — "Add Checklist Item…" silently failed to open
the popover. Keep a minimal invisible placeholder so the anchor always
has real geometry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread Sources/ContentView.swift
Comment on lines +10552 to 10554
if WorkspaceTodoFeature.checklistStyle == .popover {
statusPopoverWorkspaceId = nil
checklistPopoverWorkspaceId = workspaceId

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.

P1 Activation Token Persists This branch opens the checklist popover and leaves checklistAddFieldActivationTokens set. If the user dismisses the transient popover without adding an item, the close path clears checklistPopoverWorkspaceId but does not consume the activation token. Since the sidebar section still renders when checklistAddFieldActivationToken > 0, an empty workspace can keep stale add state after the popover closes. Clear the token when the popover add flow is dismissed without a commit, or make section visibility stop depending on a token after that flow has closed.

…line alignment

The prior zero-item placeholder fix didn't retry reliably: it depended on
an unrelated later re-render landing after the anchor NSView actually
attached to its window, so "Add Checklist Item..." on a fresh workspace
usually opened nothing until some other UI event fired. Replace it with
an AnchorView subclass that retries present() from viewDidMoveToWindow(),
so the popover opens deterministically as soon as AppKit finishes
attaching it.

The popover host's NSViewRepresentable anchor was also stacked as a
.background() spanning the whole checklist section, which suppressed
.onHover for the item rows underneath it (the hover-reveal delete "x"
rarely appeared). Shrink the anchor to a fixed 1x1pt .overlay corner so
it never intercepts hover/hit-testing, and drop the now-unnecessary
Color.clear placeholder that caused the "bottom padding" artifact.

Both the inline expansion and the popover now show all items in a
ScrollView capped at 6 rows instead of hard-clamping with a "... N more"
row, and item rows use .firstTextBaseline alignment with an
alignmentGuide offset (derived from font ascender/descender) so the
checkbox and delete button center on the first line of wrapped
multi-line item text rather than the baseline or the whole block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit aee3354. Configure here.

Comment thread Sources/ContentView.swift
.opacity(isHovered ? 1 : 0)
.allowsHitTesting(isHovered)
.accessibilityHidden(!isHovered)
.accessibilityIdentifier("SidebarChecklistPopoverRemoveItemButton")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale highlight after item removal

Low Severity

Keyboard highlight state is not cleared when an item is removed via the new hover delete button or the context-menu Remove action. Only handleAddFieldDelete resets highlightedItemId. After deleting the highlighted row, arrow-key navigation uses fallback indices and can jump to the wrong item; toggle shortcuts and Return-on-empty may no-op until the user clicks a row again.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit aee3354. Configure here.

WorkspaceTodoPaneItemRow was missing the same multi-line-wrap fix
already applied to the sidebar's inline list and popover: long item
text overflowed unwrapped past the pane's edge instead of wrapping.
Adds .fixedSize(horizontal: false, vertical: true) +
.multilineTextAlignment(.leading) to the item Text, and centers the
checkbox on the text's first line via .alignmentGuide(.firstTextBaseline).
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.

1 participant