diff --git a/server/internal/application/asset_cleanup_test.go b/server/internal/application/asset_cleanup_test.go index 79f85cb..962ba10 100644 --- a/server/internal/application/asset_cleanup_test.go +++ b/server/internal/application/asset_cleanup_test.go @@ -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() diff --git a/server/internal/application/design_service.go b/server/internal/application/design_service.go index fd7fb6f..2e968f1 100644 --- a/server/internal/application/design_service.go +++ b/server/internal/application/design_service.go @@ -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