Files
pad/internal/server/migrate_close_state.go
T
xarmian a231d0b65a fix(BUG-2367): a move or copy that would reopen, close or un-abandon an item must name the status instead of inheriting whatever survived migration (#1516)
Before this, every migrate door carried whichever done-field value survived
migration, or injected the destination default. A done task moved into
ideas silently reopened as `new`, and an abandoned item could land counted
as shipped.

Now every door (single move, bulk move, cross-workspace copy and its
preflight) compares the item's CloseState (open / done / abandoned, derived
from IsTerminalItem + IsAbandonedItem) before and after. A change is
refused unless the caller names the destination done field. Only the state
is compared, so two different values that both mean done carry without
asking. A destination with no done select field is exempt.

- move: 400 state_change_requires_value, details {field, options, from, to}
- bulk move: the same refusal in failed[]; `move` + `status` counts as supplied
- copy: 400 validation_error with the same sentence, after the unique check,
  in the preflight's order
- preflight: a needs_value row with reason `state_change`, which the web
  dialog's existing picker renders
- CLI: `hint: pad item move REF COLL --field status=<a|b|c>`

ToolSurfaceVersion 0.52 (behaviour bump). Missing needs_value routes on the
pane-menu move, CLI move and MCP are BUG-3200.

Claude-Session: https://claude.ai/code/session_01RYiqbqXmxQMoKHsGm8B77N
2026-09-24 15:53:06 -04:00

24 lines
834 B
Go

package server
import (
"encoding/json"
"github.com/PerpetualSoftware/pad/internal/models"
)
// closeStateChangeCode is the move doors' refusal when a move would change an
// item's open/done/abandoned state without the caller saying so (BUG-2367
// item 4). The copy answers the same sentence under its validation_error, and
// the preflight turns it into a needs_value row with reason `state_change`.
const closeStateChangeCode = "state_change_requires_value"
// collectionSettingsOf parses a collection's settings, treating unreadable
// settings as empty the way every other done-field caller does.
func collectionSettingsOf(c *models.Collection) models.CollectionSettings {
var settings models.CollectionSettings
if c != nil && c.Settings != "" {
_ = json.Unmarshal([]byte(c.Settings), &settings)
}
return settings
}