# Changing many issues at once

2026-08-11

Phase 7a. The query language made a set of issues addressable; this is the first
thing that does something to one.

## 🚨 It works on a selection, not on a query

The tempting design is "apply to everything matching this query". It is also how
somebody changes four thousand issues because the query was wrong in a way they
could not see, and every one of those changes is real.

So: the search screen lists the rows, the person ticks the ones they mean, and
the POST carries **issue ids**. What can be changed is what is on the screen.

That has a consequence and the consequence is the point: **a bulk change is
capped at what a page can show.** Somebody wanting to change more than that has
to narrow the query until they can see what they are about to do. A bulk change
you cannot look at is a bulk change you cannot check.

## 🚨 Every change goes through the same code as one issue

Status moves call `Workflow::apply`, exactly as the buttons on the issue page
and the drag on the board do. Not "similar code" — the same method.

A bulk path that wrote statuses directly would be a way round every rule the
workflow exists to enforce, and it would be the *easiest* way round, because
changing forty issues is precisely when somebody does not want to be asked forty
questions. That is the argument that removed `/triage` in phase 2 and shaped the
board in phase 3, and it does not get weaker because there are more rows.

## 🚨 Partial success is reported, never hidden

Forty issues, three of which the workflow refuses: the screen says **which three
and why**. Not "done", not "3 failed".

This is the whole reason the operation is synchronous. Queued, the person gets
"we will get to it" and the report arrives nowhere — which would mean building a
job-status screen for an action that takes two seconds. The cap makes the work
bounded, and bounded work can afford to answer.

## What can be changed

Status, priority, assignee, type, and sprint membership. Each is a field the
tracker already owns and already validates in one place, and bulk uses that
place.

**Not delete.** A bulk delete wants a restore path and there is not one yet —
`deleted_at` exists on a report and nothing reads it back. Offering the
destructive half of a feature whose other half does not exist is how somebody
loses forty issues and finds out that "soft" delete was not.

## The confirmation says the number

"Change 37 issues?" rather than "Are you sure?". A confirmation that does not
say what it is confirming is a dialog people learn to dismiss, and the number is
the one fact that would stop somebody who had mis-selected.

## History

Every issue gets its own history entries, exactly as if it had been changed on
its own page. A bulk change is the one people most want to audit later — "who
moved all of these to Won't fix" is a question a tracker has to be able to
answer, and it can only answer it if the rows are there.

## Not matched, deliberately

- **Bulk edit of arbitrary fields.** Jira lets an administrator change almost
  anything in bulk, including custom fields and security levels. Convoro offers
  the five above, because each additional field is another validation path that
  has to be as careful as the single-issue one, and "we support it" is not the
  same as "it is safe".
- **Undo.** A bulk change writes history and can be reversed by another bulk
  change. A one-click undo would need a transaction log of its own, and the
  history is that log — read rather than replayed.
