feat(server): place generated images beside the reference anchor node

Read a "reference anchor node:" agent directive and lay newly generated
image nodes immediately to the right of that node, inheriting its row and
falling back to its size, for planned, follow-up, and incremental flows.
Generalize the directive parser shared with the image-generator target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 09:06:02 +08:00
parent 300393dae1
commit d87cb46a9e
3 changed files with 145 additions and 4 deletions
@@ -196,6 +196,52 @@ func TestImageGeneratorTargetNodeIDParsesDirectiveCaseInsensitively(t *testing.T
}
}
func TestReferenceAnchorNodeIDParsesDirectiveCaseInsensitively(t *testing.T) {
anchorID := referenceAnchorNodeID([]design.AgentChatMessage{
{
Role: "user",
Contents: []design.AgentContent{
{Type: "text", Text: "REFERENCE ANCHOR NODE: image-2", TextSource: "settings"},
},
},
})
if anchorID != "image-2" {
t.Fatalf("expected reference anchor id, got %q", anchorID)
}
}
func TestPositionGeneratedNodesBesideReferenceKeepsAnchorAndOrdersResults(t *testing.T) {
nodes := []design.Node{
{ID: "reference-1", Type: design.NodeTypeImage, X: 100, Y: 80, Width: 500, Height: 360, Content: "https://example.com/reference.webp", Status: "success"},
}
generated := []design.Node{
{ID: "generated-1", Type: design.NodeTypeImage, Width: 400, Height: 300},
{ID: "generated-2", Type: design.NodeTypeImage, Width: 320, Height: 240},
}
positioned := positionGeneratedNodesBesideReference(nodes, "reference-1", generated)
if nodes[0].X != 100 || nodes[0].Content != "https://example.com/reference.webp" {
t.Fatalf("expected reference node to remain unchanged, got %#v", nodes[0])
}
if positioned[0].X <= nodes[0].X+nodes[0].Width || positioned[0].Y != nodes[0].Y {
t.Fatalf("expected first result beside reference, got %#v", positioned[0])
}
if positioned[1].X <= positioned[0].X+positioned[0].Width || positioned[1].Y != nodes[0].Y {
t.Fatalf("expected second result after first result, got %#v", positioned[1])
}
}
func TestPositionGeneratedNodesBesideReferenceKeepsDefaultPlacementWithoutAnchor(t *testing.T) {
generated := []design.Node{
{ID: "generated-1", Type: design.NodeTypeImage, X: 720, Y: 360, Width: 400, Height: 300},
}
positioned := positionGeneratedNodesBesideReference(nil, "missing-reference", generated)
if positioned[0].X != generated[0].X || positioned[0].Y != generated[0].Y {
t.Fatalf("expected default placement without a matching anchor, got %#v", positioned[0])
}
}
func TestMarkImageGeneratorTargetGeneratingCopiesPlaceholdersForRequestedCount(t *testing.T) {
nodes := []design.Node{
{