Files
xarmian 0cc033edd8 feat(items): content-anchored patch writes on an item with no live editor (PLAN-3578 step 1) (#2055)
POST /workspaces/{ws}/items/{ref}/patch {edits:[{old,new,line_hint?}],
expected_seq?} replaces exact text in the body. It runs through updateItem,
so it takes every PATCH guard; the body is computed inside the store
transaction from the row it replaces. Each old must occur exactly once;
edits apply in order, all or nothing; every patch writes a version. The
answer carries a unified diff of what changed.

expected_seq does not refuse: a moved seq is applied over when the
anchors still match, and answered rebased with the intervening writes,
read from a seq chain stamped into item activity entries ({prev_seq, seq,
version_id}). complete is decided at runtime, so any write that left no
stamp reads incomplete; a guard test classifies every seq-moving store
statement. Refusals write nothing: patch_anchor_missing,
patch_anchor_ambiguous, patch_room_open (an open editor; step 2).

CLI: pad item patch, pad item show --lines. MCP 0.87 (additive):
pad_item.patch, get lines, three allow-listed codes. The timeline treats
the chain and patch keys as bookkeeping, not a change.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-11 06:20:09 -04:00

33 lines
1022 B
Go

package bodypatch
import "testing"
func TestParseLineRange(t *testing.T) {
for in, want := range map[string][2]int{"3": {3, 3}, "2-5": {2, 5}, "4-": {4, 0}, " 1-1 ": {1, 1}} {
s, e, err := ParseLineRange(in)
if err != nil || s != want[0] || e != want[1] {
t.Errorf("%q: %d %d %v", in, s, e, err)
}
}
for _, bad := range []string{"", "0", "-3", "5-2", "a-b", "2-x"} {
if _, _, err := ParseLineRange(bad); err == nil {
t.Errorf("%q accepted", bad)
}
}
}
func TestSliceBodyLines(t *testing.T) {
lines, from, to, total := SliceBodyLines("a\nb\nc\n", 2, 0)
if total != 3 || from != 2 || to != 3 || len(lines) != 2 || lines[1].N != 3 || lines[1].Text != "c" {
t.Fatalf("got %+v %d %d %d", lines, from, to, total)
}
lines, _, to, total = SliceBodyLines("a\nb", 1, 9)
if total != 2 || to != 2 || len(lines) != 2 {
t.Fatalf("clip: %+v %d %d", lines, to, total)
}
lines, _, _, total = SliceBodyLines("", 1, 0)
if total != 0 || len(lines) != 0 {
t.Fatalf("empty body: %+v %d", lines, total)
}
}