fix(tools): describe visible strokes (#447)
- Include stroke color and rounded weight in node and child summaries - Select the first visible stroke when hidden paints precede it - Cover both describe output paths with mixed stroke visibility Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
This commit is contained in:
parent
90d1d37113
commit
9f80c35838
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- Show stroke colors and weights in AI visual descriptions. (#447)
|
||||
- Stop warning AI agents that supported inline SVG attributes were ignored. (#445)
|
||||
- Help AI agents discover every shape supported by `create_shape`. (#448)
|
||||
- Keep `fill="none"` and `stroke="none"` SVG paths transparent when rendering inline artwork. (#446)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ function findSolidFillIndex(node: SceneNode): number {
|
|||
return node.fills.findIndex((candidate) => candidate.type === 'SOLID' && candidate.visible)
|
||||
}
|
||||
|
||||
function strokeSummary(node: SceneNode): string | null {
|
||||
const stroke = node.strokes.find((candidate) => candidate.visible)
|
||||
if (!stroke) return null
|
||||
const weight = Math.round(stroke.weight * 100) / 100
|
||||
return `${colorToHex(stroke.color)} ${weight}px stroke`
|
||||
}
|
||||
|
||||
export function describeVisual(node: SceneNode, graph?: SceneGraph): string {
|
||||
const parts: string[] = []
|
||||
const fillIndex = findSolidFillIndex(node)
|
||||
|
|
@ -23,7 +30,8 @@ export function describeVisual(node: SceneNode, graph?: SceneGraph): string {
|
|||
const fill = node.fills[fillIndex]
|
||||
parts.push(`${colorToHex(fill.color)}${boundFillSuffix(node, fillIndex, graph)} fill`)
|
||||
}
|
||||
if (node.strokes.length > 0 && node.strokes[0]?.visible) parts.push('bordered')
|
||||
const stroke = strokeSummary(node)
|
||||
if (stroke) parts.push(stroke)
|
||||
if (node.cornerRadius > 0) parts.push('rounded')
|
||||
if (node.clipsContent) parts.push('clipped')
|
||||
for (const effect of node.effects) {
|
||||
|
|
@ -83,6 +91,8 @@ export function summarizeContainer(node: SceneNode, graph?: SceneGraph): string
|
|||
const fill = node.fills[fillIndex]
|
||||
parts.push(`${colorToHex(fill.color)}${boundFillSuffix(node, fillIndex, graph)}`)
|
||||
}
|
||||
const stroke = strokeSummary(node)
|
||||
if (stroke) parts.push(stroke)
|
||||
if (node.cornerRadius > 0) parts.push('rounded')
|
||||
const layout = describeLayout(node)
|
||||
if (layout) parts.push(layout)
|
||||
|
|
|
|||
67
tests/engine/tools/describe.test.ts
Normal file
67
tests/engine/tools/describe.test.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import type { Color, Stroke } from '@open-pencil/scene-graph'
|
||||
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { getTool, setupToolTest, type ToolResult } from '#tests/helpers/tools'
|
||||
|
||||
const NAVY: Color = { r: 2 / 255, g: 26 / 255, b: 59 / 255, a: 1 }
|
||||
|
||||
const HIDDEN_STROKE: Stroke = {
|
||||
color: { r: 1, g: 0, b: 0, a: 1 },
|
||||
weight: 4,
|
||||
opacity: 1,
|
||||
visible: false,
|
||||
align: 'CENTER',
|
||||
cap: 'NONE',
|
||||
join: 'MITER'
|
||||
}
|
||||
|
||||
const VISIBLE_STROKE: Stroke = {
|
||||
color: NAVY,
|
||||
weight: 10.126,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
align: 'CENTER',
|
||||
cap: 'NONE',
|
||||
join: 'MITER'
|
||||
}
|
||||
|
||||
interface ChildSummary {
|
||||
summary: string
|
||||
}
|
||||
|
||||
function setupStrokedChild() {
|
||||
const { figma, graph } = setupToolTest()
|
||||
const frame = figma.createFrame()
|
||||
frame.name = 'Outline'
|
||||
frame.resize(200, 200)
|
||||
|
||||
const outline = figma.createRectangle()
|
||||
outline.resize(100, 4)
|
||||
frame.appendChild(outline)
|
||||
graph.updateNode(outline.id, {
|
||||
fills: [],
|
||||
strokes: [HIDDEN_STROKE, VISIBLE_STROKE]
|
||||
})
|
||||
|
||||
return { figma, frameId: frame.id, outlineId: outline.id }
|
||||
}
|
||||
|
||||
describe('describe stroke summaries', () => {
|
||||
test('reports the first visible stroke in a child summary', () => {
|
||||
const { figma, frameId } = setupStrokedChild()
|
||||
const result = getTool('describe').execute(figma, { id: frameId }) as ToolResult
|
||||
const children = result.children as ChildSummary[]
|
||||
const summary = expectDefined(children[0], 'stroked child summary').summary
|
||||
|
||||
expect(summary).toContain('#021A3B 10.13px stroke')
|
||||
})
|
||||
|
||||
test('reports the first visible stroke in the node visual summary', () => {
|
||||
const { figma, outlineId } = setupStrokedChild()
|
||||
const result = getTool('describe').execute(figma, { id: outlineId }) as ToolResult
|
||||
|
||||
expect(result.visual).toContain('#021A3B 10.13px stroke')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue