fix(design): preserve existing canvas nodes when compressed snapshot omits them
SaveCanvas treated a nil Nodes/Connections slice from a compressed snapshot as an empty canvas, wiping existing content. Distinguish nil (field omitted, keep the project's current nodes/connections) from an explicit empty slice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -182,6 +182,60 @@ func TestSaveCanvasAcceptsCompressedSnapshotPayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveCanvasPreservesNodesWhenCompressedSnapshotOmitsThem(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := memory.NewProjectRepository()
|
||||
service := NewDesignService(repo, nil, nil, nil, nil)
|
||||
service.now = func() time.Time {
|
||||
return time.Unix(101, 0).UTC()
|
||||
}
|
||||
project := design.Project{
|
||||
ID: "project-null-snapshot-nodes",
|
||||
Title: "Null snapshot nodes",
|
||||
Status: design.StatusReady,
|
||||
UpdatedAt: time.Unix(100, 0).UTC(),
|
||||
Version: "100000",
|
||||
SnapshotID: "snapshot-old",
|
||||
Viewport: design.Viewport{X: 1, Y: 2, K: 1},
|
||||
Nodes: []design.Node{
|
||||
{ID: "existing-image", Type: design.NodeTypeImage, Title: "Existing", Content: "http://localhost:19000/canvas-assets/existing.png", Status: "success"},
|
||||
},
|
||||
Connections: []design.Connection{
|
||||
{ID: "existing-connection", FromNodeID: "existing-image", ToNodeID: "existing-image"},
|
||||
},
|
||||
}
|
||||
if err := repo.Save(ctx, project); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshotProject := project
|
||||
snapshotProject.SnapshotID = "snapshot-client"
|
||||
snapshotProject.Viewport = design.Viewport{X: 12, Y: 34, K: 0.8}
|
||||
snapshotProject.Nodes = nil
|
||||
snapshotProject.Connections = nil
|
||||
canvas, err := design.EncodeCanvasSnapshot(snapshotProject)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
saved, err := service.SaveCanvas(ctx, project.ID, design.Viewport{}, nil, nil, "", "", canvas)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if saved.SnapshotID != "snapshot-client" {
|
||||
t.Fatalf("expected client snapshot id, got %s", saved.SnapshotID)
|
||||
}
|
||||
if saved.Viewport != snapshotProject.Viewport {
|
||||
t.Fatalf("unexpected viewport: %#v", saved.Viewport)
|
||||
}
|
||||
if len(saved.Nodes) != 1 || saved.Nodes[0].ID != "existing-image" {
|
||||
t.Fatalf("expected existing nodes to be preserved, got %#v", saved.Nodes)
|
||||
}
|
||||
if len(saved.Connections) != 1 || saved.Connections[0].ID != "existing-connection" {
|
||||
t.Fatalf("expected existing connections to be preserved, got %#v", saved.Connections)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveCanvasDoesNotDeleteImageAssetReferencedByAgentMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := memory.NewProjectRepository()
|
||||
|
||||
@@ -1082,10 +1082,18 @@ func (s *DesignService) SaveCanvas(ctx context.Context, projectID string, viewpo
|
||||
return design.Project{}, fmt.Errorf("%w: invalid canvas snapshot", design.ErrInvalidInput)
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
nodes = snapshot.Nodes
|
||||
if snapshot.Nodes != nil {
|
||||
nodes = snapshot.Nodes
|
||||
} else {
|
||||
nodes = append([]design.Node(nil), project.Nodes...)
|
||||
}
|
||||
}
|
||||
if len(connections) == 0 {
|
||||
connections = snapshot.Connections
|
||||
if snapshot.Connections != nil {
|
||||
connections = snapshot.Connections
|
||||
} else {
|
||||
connections = append([]design.Connection(nil), project.Connections...)
|
||||
}
|
||||
}
|
||||
if viewport.K == 0 {
|
||||
viewport = snapshot.Viewport
|
||||
|
||||
Reference in New Issue
Block a user