Fix icon positioning after insert, add emoji guidance to system prompt

- Zero icon frame x/y after appendChild to parent — fixes icons appearing
  at viewport origin instead of inside their parent container
- Add name→names fallback to insert_icon for backward compatibility with
  single-icon calls (name param still works, returns legacy format)
- Add emoji section to system prompt: emoji show as □ tofu on canvas,
  use insert_icon for UI iconography, emoji only in user content/reactions
- Strengthen flex requirement in layout docs: every parent with fill
  children MUST have flex='col'|'row'
This commit is contained in:
Anton A S 2026-03-11 16:58:49 +03:00
parent dec0a67e4f
commit dd91b682d1
4 changed files with 50 additions and 28 deletions

12
components.d.ts vendored
View file

@ -11,7 +11,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
APIKeySetup: typeof import('./src/components/chat/APIKeySetup.vue')['default']
AppearanceSection: typeof import('./src/components/properties/AppearanceSection.vue')['default']
AppMenu: typeof import('./src/components/AppMenu.vue')['default']
AppSelect: typeof import('./src/components/AppSelect.vue')['default']
@ -35,7 +34,6 @@ declare module 'vue' {
IconLucideALargeSmall: typeof import('~icons/lucide/a-large-small')['default']
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideAlignCenter: typeof import('~icons/lucide/align-center')['default']
IconLucideAlignHorizontalDistributeCenter: typeof import('~icons/lucide/align-horizontal-distribute-center')['default']
IconLucideAlignHorizontalJustifyCenter: typeof import('~icons/lucide/align-horizontal-justify-center')['default']
IconLucideAlignHorizontalJustifyEnd: typeof import('~icons/lucide/align-horizontal-justify-end')['default']
IconLucideAlignHorizontalJustifyStart: typeof import('~icons/lucide/align-horizontal-justify-start')['default']
@ -47,10 +45,6 @@ declare module 'vue' {
IconLucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
IconLucideArrowRight: typeof import('~icons/lucide/arrow-right')['default']
IconLucideBaseline: typeof import('~icons/lucide/baseline')['default']
IconLucideBetweenHorizontalEnd: typeof import('~icons/lucide/between-horizontal-end')['default']
IconLucideBetweenHorizontalStart: typeof import('~icons/lucide/between-horizontal-start')['default']
IconLucideBetweenVerticalEnd: typeof import('~icons/lucide/between-vertical-end')['default']
IconLucideBetweenVerticalStart: typeof import('~icons/lucide/between-vertical-start')['default']
IconLucideBlend: typeof import('~icons/lucide/blend')['default']
IconLucideBold: typeof import('~icons/lucide/bold')['default']
IconLucideBot: typeof import('~icons/lucide/bot')['default']
@ -67,9 +61,7 @@ declare module 'vue' {
IconLucideFlipVertical: typeof import('~icons/lucide/flip-vertical')['default']
IconLucideFolderPlus: typeof import('~icons/lucide/folder-plus')['default']
IconLucideImage: typeof import('~icons/lucide/image')['default']
IconLucideImageOff: typeof import('~icons/lucide/image-off')['default']
IconLucideItalic: typeof import('~icons/lucide/italic')['default']
IconLucideKeyRound: typeof import('~icons/lucide/key-round')['default']
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideLayoutGrid: typeof import('~icons/lucide/layout-grid')['default']
IconLucideLink: typeof import('~icons/lucide/link')['default']
@ -77,8 +69,7 @@ declare module 'vue' {
IconLucideMenu: typeof import('~icons/lucide/menu')['default']
IconLucideMessageCircle: typeof import('~icons/lucide/message-circle')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideMoveHorizontal: typeof import('~icons/lucide/move-horizontal')['default']
IconLucideMoveVertical: typeof import('~icons/lucide/move-vertical')['default']
IconLucidePlay: typeof import('~icons/lucide/play')['default']
IconLucidePlus: typeof import('~icons/lucide/plus')['default']
IconLucideRadius: typeof import('~icons/lucide/radius')['default']
IconLucideRedo2: typeof import('~icons/lucide/redo2')['default']
@ -91,7 +82,6 @@ declare module 'vue' {
IconLucideShare2: typeof import('~icons/lucide/share2')['default']
IconLucideSidebar: typeof import('~icons/lucide/sidebar')['default']
IconLucideSlidersHorizontal: typeof import('~icons/lucide/sliders-horizontal')['default']
IconLucideSpace: typeof import('~icons/lucide/space')['default']
IconLucideSparkles: typeof import('~icons/lucide/sparkles')['default']
IconLucideSquare: typeof import('~icons/lucide/square')['default']
IconLucideStrikethrough: typeof import('~icons/lucide/strikethrough')['default']

View file

@ -247,7 +247,11 @@ function createIconFrame(
if (parentId) {
const parent = figma.getNodeById(parentId)
if (parent) parent.appendChild(frame)
if (parent) {
parent.appendChild(frame)
frame.x = 0
frame.y = 0
}
}
for (const path of icon.paths) {
@ -297,21 +301,27 @@ export const insertIcon = defineTool({
params: {
names: {
type: 'string[]',
description: 'Icon names as prefix:name (e.g. ["lucide:heart"] or ["lucide:heart","lucide:home","lucide:star"])',
required: true
description: 'Icon names as prefix:name (e.g. ["lucide:heart"] or ["lucide:heart","lucide:home","lucide:star"])'
},
name: {
type: 'string',
description: 'Single icon name (shorthand for names with one icon)'
},
size: { type: 'number', description: 'Icon size in pixels (default: 24)' },
color: { type: 'color', description: 'Icon color hex (replaces currentColor). Default: #000000' },
parent_id: { type: 'string', description: 'Parent node ID for all icons' }
},
execute: async (figma, args) => {
const names = args.names ?? (args.name ? [args.name] : [])
if (names.length === 0) return { error: 'Provide "names" (array) or "name" (string)' }
const size = args.size ?? 24
const color = args.color ?? '#000000'
const parsedColor = parseColor(color)
let icons
try {
icons = await fetchIcons(args.names, size)
icons = await fetchIcons(names, size)
} catch (e) {
return { error: (e as Error).message }
}
@ -319,7 +329,7 @@ export const insertIcon = defineTool({
const inserted: { id: string; name: string; icon: string }[] = []
const notFound: string[] = []
for (const name of args.names) {
for (const name of names) {
const icon = icons.get(name)
if (!icon || icon.paths.length === 0) {
notFound.push(name)
@ -329,6 +339,10 @@ export const insertIcon = defineTool({
inserted.push({ id: frame.id, name: frame.name, icon: name })
}
if (args.name && inserted.length === 1) {
return { id: inserted[0].id, name: inserted[0].name, type: 'FRAME' as const }
}
const result: Record<string, unknown> = { inserted }
if (notFound.length > 0) result.not_found = notFound
return result

View file

@ -248,7 +248,7 @@ export function serializeChatLog(messages: UIMessage[]): string {
parts.push(` ${p.text as string}`)
} else if (p.type === 'reasoning') {
parts.push(
` [reasoning] ${typeof p.text === 'string' ? p.text : (typeof p.content === 'string' ? p.content : '')}`
` [reasoning] ${typeof p.text === 'string' ? p.text : typeof p.content === 'string' ? p.content : ''}`
)
} else if (
p.type === 'tool-invocation' ||

View file

@ -32,6 +32,8 @@ These are ALL available props. Nothing else exists — no lineHeight, no letterS
Without flex, children are positioned by x/y (absolute). With flex, x/y are ignored and children flow automatically.
⚠ **Every parent that has children using `w="fill"` or `h="fill"` MUST have `flex="col"` or `flex="row"`.** Without flex, fill sizing is ignored and children revert to fixed/hug size. This is the #1 layout bug — forgetting flex on a parent container.
justify/items require flex — always declare flex="row" or flex="col" before using alignment props. The value is "between", not "space-between".
A hug parent shrinks to fit its children. A fill child stretches to its parent. These can't be circular — if the parent hugs, at least one child must have a concrete size.
@ -207,20 +209,36 @@ Three tools for Iconify icons (200k+ icons). All batch-friendly — minimize LLM
**Popular icon sets:**
| Prefix | Style | Best for |
|--------|-------|----------|
| `lucide` | Outline/stroke | UI, minimal |
| `mdi` | Filled | General purpose |
| `heroicons` | Outline/stroke | Web apps |
| `tabler` | Outline/stroke | Dashboards |
| `solar` | Mixed | Modern UI |
| `mingcute` | Filled | Apps, mobile |
| `ri` | Filled | Web, editorial |
| `iconoir` | Outline/stroke | Clean, minimal |
| `ph` | Multiple weights | Versatile |
| Prefix | Style | Best for |
| ----------- | ---------------- | --------------- |
| `lucide` | Outline/stroke | UI, minimal |
| `mdi` | Filled | General purpose |
| `heroicons` | Outline/stroke | Web apps |
| `tabler` | Outline/stroke | Dashboards |
| `solar` | Mixed | Modern UI |
| `mingcute` | Filled | Apps, mobile |
| `ri` | Filled | Web, editorial |
| `iconoir` | Outline/stroke | Clean, minimal |
| `ph` | Multiple weights | Versatile |
⚠ Always set `color` on `insert_icon` — default is black, invisible on dark backgrounds.
## Emoji ⚠
**Do NOT use emoji (📷 🥾 ☕ 🎨 ♡ ✓ ▶ ★ etc.) in UI elements.** The canvas renders text with system fonts — emoji glyphs are missing and show as "□" (tofu). This includes:
- Icon buttons, badges, navigation items, tags, chips
- Checkmarks, hearts, stars, arrows — use `insert_icon` instead
- Category labels like "📷 Photography" — just write "Photography"
**Emoji ARE acceptable** only in user-generated content where they would naturally appear:
- Chat messages, social posts, comments, reviews
- Emoji reactions (👍 ❤️ 😂) on messages
- Story/status text
For UI iconography, always use `insert_icon` with Lucide or other Iconify sets. For checkmarks use `insert_icon(names=["lucide:check"])`, for hearts `insert_icon(names=["lucide:heart"])`, etc.
## Shapes
All visual elements (Rectangle, Ellipse, Star, Polygon, Line) have **no fill by default** — they render as invisible without `bg="#hex"` or `stroke="#hex"`.