Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-262Done

bug-backlog-selection-bar-move-to-backlog-always-shown Backlog selection bar: "Move to backlog" button is shown even when every selected item is already in the backlog (should hide / disable)

Type: bug · Parent: Epic 4 (current epic — discovered during Epic 4 work) · Code surface owned by: Story 4.2 (Backlog UI), Subtask 4.2.5 — the selection bar + its bulk-action buttons · Status: open · Reported by: Yue.

On the /backlog page, selecting one or more rows reveals the selection bar with two bulk-move buttons: Move to sprint ▸ and Move to backlog. The "Move to backlog" button is rendered unconditionally whenever selectedIds.size > 0 — INCLUDING when every selected row is already in the backlog (sprint_id IS NULL). In that case the button is a no-op (moving backlog items "to the backlog" is meaningless), but it sits as an active-looking control on the bar. The Jira backlog (rung 1) doesn't do this: its bulk-action set is contextual to where the selected items currently live. The user surfaced this in the backlog screenshot (four rows selected, all of them backlog items, and "移动到待办列表" still appears next to "移动到 Sprint").

Repro: sign in as zhuyue@prodect.co / !QAZ1qaz, open the moooon / prodect project → /backlog. The page mounts with several backlog rows below the sprint containers. Click one (or shift-click a range) of the BACKLOG rows — the selection bar appears at the top of the stack with N selected + Move to sprint ▸ + Move to backlog + Clear. Observe that Move to backlog is enabled even though every selected row is already in the backlog. Clicking it dispatches a no-op bulk write (bulkMoveToBacklog with rows that already have sprint_id IS NULL — the service currently treats it as a guarded no-op, but the UI affordance still suggests an action is available).

Root cause. app/(authed)/backlog/_components/SelectionBar.tsx lines 61–68 render the Move to backlog button unconditionally — there is no check against the selected items' current sprint membership. The useBacklogDnd() hook (the coordinator that owns selectedIds) does have the per-item sprint membership available (it has to, because that is what bulkAssignToSprint / bulkMoveToBacklog operate on). The bar just does not consult it.

Fix shape (decide at fix time — both are defensible).

  1. HIDE the button when every selected item is already in the backlog. Most Jira-faithful (Jira hides irrelevant bulk actions); cleanest visually. Symmetric counterpart : hide Move to sprint ▸ when every selected item is already in the SAME sprint (can the same selection span multiple sprints? today yes, since the bar shows over a mixed selection; preserve that case).
  2. DISABLE the button (greyed + aria-disabled) when every selected item is already in the backlog, with a tooltip explaining why. Slightly more discoverable than hiding, but adds a tooltip primitive call. Either way, the gate reads the same: someSelectedItemIsInASprint = ids.some(id => itemSprintIdById.get(id) != null). Plumb a getSprintIdFor(id) (or the existing item-by-id map) out of useBacklogDnd() to SelectionBar, derive the booleans, gate each button. No service / API change — purely a UI gate on existing state.

Symmetric gap (in scope for the same fix). Apply the mirror rule to Move to sprint ▸ too: if every selected item is already in the SAME sprint, hide/disable that button (submenu would only re-pick the same sprint, a no-op). The two gates are the same shape and ship together — leaving one untreated would be the same finding-#33 / mirror-product shortfall on the symmetric branch.

Acceptance criteria

  • When the selection bar is open and every selected item has sprint_id IS NULL (already in the backlog), the Move to backlog button is hidden (preferred) or visibly disabled with an explanatory tooltip.
  • When the selection contains at least one item with sprint_id IS NOT NULL (i.e. currently in a sprint), the Move to backlog button remains visible + enabled and still moves that subset to the backlog (mixed selections behave as today).
  • Symmetric: when every selected item is in the SAME sprint, the Move to sprint ▸ button is hidden/disabled (re-picking the same sprint is a no-op).
  • Clear and the N selected count are unaffected; the selection model and the multi-select drag path are unchanged.
  • A component test asserts BOTH branches: (a) all-backlog selection → no Move to backlog button rendered; (b) mixed selection (≥1 sprint item + ≥1 backlog item) → both buttons rendered. Symmetric branch tested too: all-same-sprint selection → no Move to sprint ▸ button.
  • AA contrast / keyboard nav / focus return preserved; no change to bulkAssignToSprint / bulkMoveToBacklog service signatures.

Context refs

  • app/(authed)/backlog/_components/SelectionBar.tsx lines 61–68 — the unconditional render site (the fix site)
  • app/(authed)/backlog/_components/BacklogDndProvider.tsx — the useBacklogDnd coordinator that owns selectedIds + the per-item sprint membership the gate needs; plumb a getSprintIdFor(id) (or expose the item-by-id map) for the gate to read
  • Story 4.2.5 (multi-select + atomic bulk move) — the subtask that shipped the bar without the gate; AC said selection bar shows "N selected" + Move to sprint ▸ + Move to backlog but did not specify contextual gating (the AC undershot — flag it as the plan gap that lets the bug exist)
  • design/backlog/backlog.mock.html panel 4 — the multi-select bar spec; verify whether the mockup specifies the contextual gate (if not, this is also a design-notes addendum, not a design rework — the gate is a behaviour spec, not a layout change)
  • Mirror: Jira backlog selection bar — actions are contextual to the selection origin (rung 1)
  • Related: bug-backlog-zh-sprint-translated-as-chongci (sibling Epic-4 bug filed in the same session — same surface, different shape)