Files
multica/server/internal/issueposition/position.go
aa4c504c78 fix(issues): re-rank an issue when its status moves it to a new column (#7422)
position ranks an issue inside its own (workspace, status) column, but a
status change carried the old column's value over unchanged. The value
that put an issue on top of Todo is roughly zero, and lands it below
every hand-dragged issue in Done — so agent-completed work sank into the
middle of a long Done column and read as "the task disappeared".

Re-rank to the top of the destination column on both status-write paths,
reusing the MIN(position) - 1 policy new issues already get. An explicit
position still wins, so cross-column drag-and-drop keeps the slot it
dropped on, and a same-status write leaves position alone.

The re-rank reads MIN(position) for the destination (workspace, status),
which idx_issue_status could only serve with a heap fetch per issue in
the column — paid on every completion against the column that grows
without bound. Add (workspace_id, status, position) to make it an
index-only min.

Closes #7410

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-23 12:36:10 +08:00

31 lines
955 B
Go

package issueposition
import (
"context"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
type queryRower interface {
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
// NextTopPosition returns a position that sorts before every existing issue in
// the workspace/status column when manual sorting orders by position ASC.
//
// UpdateIssue and UpdateIssueStatus in queries/issue.sql inline the same
// MIN(position) - 1 policy to re-rank an issue whose status moves it to a
// different column. Change them together.
func NextTopPosition(ctx context.Context, q queryRower, workspaceID pgtype.UUID, status string) (float64, error) {
var minPos float64
if err := q.QueryRow(ctx,
`SELECT COALESCE(MIN(position), 0) FROM issue WHERE workspace_id = $1 AND status = $2`,
workspaceID, status,
).Scan(&minPos); err != nil {
return 0, fmt.Errorf("query min issue position: %w", err)
}
return minPos - 1, nil
}