Tag Overrides Workflow - Illustrations
How to add per-item visual tags to illustrations so semantic search (happy, running, book, burger) returns the right items.
Why this exists
Section titled “Why this exists”Build-time generator tools/build-plugin-library-data.mjs auto-tags each illustration by category only:
| Section | Category rule-tags |
|---|---|
junior (Junior cat) | [character, pose, junior] |
junior (Friends cat) | [character, group, friends, junior] |
object (Topic cat) | [object, topic, subject] |
cuisine (Cuisine cat) | [food, meal, cuisine] |
Category tags make broad queries work: kid → all 101 junior+friends items; food → all 113 cuisine items.
But specific visual content (boy vs girl, smiling vs surprised, holding pencil vs piano, burger vs egg vs vegetable) is invisible to the generator. That’s what overrides cover.
Input file
Section titled “Input file”design-guideline/figma-ai/tag-overrides-illustrations.json
{ "_meta": { /* doc + coverage stats */ }, "items": { "23393:1345": { "_name": "Dol Junior 01", "tags": ["boy", "kid", "happy", "smile", "pencil", "write", "edit"] } }}- Key = Figma node id (with
:, exactly as in the cache file) _nameis optional documentation - ignored by the generator, helpful for humans diffingtagsis the only field read; merged with rule-tags then deduped and capped at 12 total per item
Tag style guide
Section titled “Tag style guide”3–7 visual tags per item. Less = under-tagged (queries miss the item). More = noise + cap clipping.
Lowercase, single English word per tag.
- ✅
boy,helmet,surprised,holding,pencil - ❌
Boy,red helmet(multi-word),Boy holding pencil
Tag what’s visually present, not what’s implied:
- ✅ Junior 01 holds a giant pencil → tag
pencil, write, holding - ❌ Don’t tag
studentjust because the figure looks studious - too inferential
Cover up to 7 dimensions for character illustrations (V0.2.2 expanded):
- Subject -
boy / girl / kid / friends / group / teacher - Emotion -
happy / sad / surprised / confused / angry / excited / sick - Pose (body position) -
standing / sitting / running / waving / holding - Activity (V0.2.2; verb form, what-is-happening) -
cooking / reading / sleeping / dancing / exercising / working / shopping / driving / painting - Object held / context -
pencil / book / piano / helmet / ball / lightbulb - Setting (V0.2.2; where) -
school / office / home / outdoor / kitchen / library / beach / city - Body part (V0.2.2; helps emoji + character search) -
face / hand / eye / mouth / ear / hair
Pose vs Activity rule: pose = how the body is positioned (sitting / standing / running). activity = what the person is doing (cooking / reading / studying). A character can have both - e.g., a kid sitting (pose) + reading (activity) + book (object).
Cover up to 5 dimensions for object/cuisine illustrations (V0.2.2 expanded):
- Specific name -
burger / egg / tomato / medal / brain / armchair / coin - Family -
vegetable / pastry / award / spice / furniture / currency - Function / activity (V0.2.2) -
cooking / seasoning / hydration / payment - Setting (V0.2.2; if scene-like) -
kitchen / library / office - Visual / context -
colorful / outline / monochrome / 3d(only if visually distinctive)
Time / season (V0.2.2; cross-cutting, applies to either): morning / evening / night / summer / winter / spring / autumn. Tag when the illustration depicts that time/season explicitly (sunrise scene, beach summer, winter snow).
Don’t repeat rule-tags. Items already have their category defaults. Adding character to a Junior item override is wasted slot.
Lean on the taxonomy (figma-plugin/dol-library/src/ui/data/tag-taxonomy.ts) - synonyms expand at search time:
- Tag
surprised→ querywow/amazed/shockfinds the item (taxonomy expansion) - Tag
confused→ queryworried/puzzled/wonderingfinds it - Tag
book→ queryreading/study/textbookfinds it
So you don’t need to bake every variation into the override - pick one canonical and the search engine bridges the rest.
Step-by-step authoring
Section titled “Step-by-step authoring”- Open the illustration thumbnail at
studio/public/ds-assets/illustrations/<section>/<id-with-dash>.png. - Look up the Figma node id in
design-guideline/figma-ai/cache/illustrations-extracted-2026-05-04.json(or click the item in Studio web - id shown in the URL slug). - Draft 3–7 tags per the dimensions above.
- Add to
tag-overrides-illustrations.json:"<nodeId>": { "_name": "<name>", "tags": [...] } - Run
node tools/build-plugin-library-data.mjs- re-emits the manifest + plugin generated data. - (Optional)
node tools/build-plugin-library-data.mjs --checkconfirms outputs are committed up-to-date for CI. - Spot-check by searching the new tag in Studio web (
localhost:4000/design-system/#illustrations) or the Figma plugin panel.
When to extend the taxonomy itself
Section titled “When to extend the taxonomy itself”The taxonomy lives at figma-plugin/dol-library/src/ui/data/tag-taxonomy.ts (canonical) and studio/src/data/tag-taxonomy.ts (mirror copy - keep in sync via cp).
Add a new canonical when the same visual concept appears in 5+ items AND has obvious user-typed variations:
- e.g.
confused: { synonyms: ['worried', 'puzzled', 'wondering', 'unsure'] }was added when 3+ Junior items showed worried/confused poses.
Add a new synonym to existing canonical when you tag with a term and want existing queries to find it:
- e.g. tagging
eggsitems - could addeggstobreakfast.synonymssobreakfastquery hits.
DO NOT add a synonym that’s also a synonym of a different group’s canonical without thinking - that creates a transitive bridge that over-matches. The expander is single-hop only (no fixed-point closure) precisely because cross-group bridges (e.g. class → course AND group) caused book queries to match friends items in early prototyping. See header comment in tag-taxonomy.ts buildSynonymExpander().
Coverage policy
Section titled “Coverage policy”V0.2.3 status (2026-05-05): 100% coverage achieved. All 293 illustrations have curated visual tags via the autonomous 5-agent vision-tag pipeline (see “Autonomous bulk-tag mechanism” below). Pre-this-session pilot was 45/293; Phase D added 248 in one session.
Pre-V0.2.3 history:
- V0.2 (15-item pilot): junior+topic+cuisine demo
- V0.2.2 (45/293 = 15%): added 21 dimension-coverage items + Phase B taxonomy
- V0.2.3 (293/293 = 100%): Phase D autonomous bulk-tag
Verification: query battery 90.9% on standard 77-query suite, 96.9% on extended 96-query suite covering all user-mentioned dimensions (gender / emotion / activity / object props / food / VN aliases). Genuine catalog gaps: no sleeping/dancing/office/morning illustrations exist in the source set - agents tagged what’s visually present.
Autonomous bulk-tag mechanism (Phase D pattern, repeat for future illustrations)
Section titled “Autonomous bulk-tag mechanism (Phase D pattern, repeat for future illustrations)”When new illustration batches ship (cache JSON regenerates with new node IDs), use this autonomous pipeline instead of single-session manual tagging.
When to trigger
Section titled “When to trigger”- New illustrations imported (cache file delta shows N new IDs)
- Existing items become untagged after cache regeneration (rare - node ID stability protects this)
- Recall measurably degrades on relevant query battery (≥10 query failures)
Pipeline (5-step, ~10 min)
Section titled “Pipeline (5-step, ~10 min)”-
Identify untagged set:
Terminal window node -e "const cache = JSON.parse(require('fs').readFileSync('design-guideline/figma-ai/cache/illustrations-extracted-2026-05-04.json','utf8'));const overrides = JSON.parse(require('fs').readFileSync('design-guideline/figma-ai/tag-overrides-illustrations.json','utf8'));const tagged = new Set(Object.keys(overrides.items));const buckets = { junior: [], object: [], cuisine: [] };for (const s of ['junior','object','cuisine']) for (const it of cache.sections[s].items) if (!tagged.has(it.id)) buckets[s].push({id:it.id, name:it.name, section:s, thumb:'studio/public/ds-assets/illustrations/'+s+'/'+it.id.replace(':','-')+'.png'});// Save batches: junior split 50/50, object as one, cuisine split 50/50" -
Dispatch 5 parallel sub-agents (general-purpose, sonnet) - each batch ≤50 items to fit context budget:
- Agent 1 + 2: junior+friends split (advisor: characters per batch ≤50)
- Agent 3: objects/topics (single batch - semantic cohesion)
- Agent 4 + 5: cuisine split (advisor: cuisine in same prompt = consistent canonical food names; split allowed at 47-50/agent)
-
Agent prompt template lives in commit
<phase-d-commit>. Key elements:- 7-dim character rubric / 5-dim object rubric (lifted from this file)
- Explicit canonical food name list (~80 names) for cuisine consistency across agents
- Junior-with-helmet always-include tags:
boy / kid / helmet - Output strict JSON with
{ "items": { "<figmaId>": { "_name": "...", "tags": [...] } } } - Agents run with
run_in_background: truefor parallel execution
-
Merge results into
tag-overrides-illustrations.json:Terminal window node -e "const overrides = JSON.parse(require('fs').readFileSync('design-guideline/figma-ai/tag-overrides-illustrations.json','utf8'));for (let i = 1; i <= 5; i++) {const agent = JSON.parse(require('fs').readFileSync('/tmp/agent-out-' + i + '.json','utf8'));for (const [id, entry] of Object.entries(agent.items)) {if (!overrides.items[id]) overrides.items[id] = entry;}}// Recompute _meta.coverage.byCategory + tagged + lastUpdatedrequire('fs').writeFileSync('design-guideline/figma-ai/tag-overrides-illustrations.json', JSON.stringify(overrides, null, 2) + '\n');" -
Verify + deploy:
node tools/build-plugin-library-data.mjs→ regenerates manifest + plugin gen- Run query battery (
/tmp/query-battery.mtsor your own) - expect ≥85% pass npm testinpackages/library-shell(21+ tests, must stay green)cd studio && npm run build→ studio dist with new manifest- Worktree pages-static + rsync + force-push (see V0.2.2 deploy commit for exact command)
Token cost calibration
Section titled “Token cost calibration”Per ai-memory/lessons/vision-tagging-cost-discipline-2026-05-05.md:
- 248 items × ~10K tokens vision = 1.5–2.5M tokens TOTAL across 5 agent contexts
- Each agent ≤200K context (sonnet) - fits with ~50 items × 1500-tok vision input
- Returned to orchestrator: ~10K tokens text per agent = ~50K total merged JSON
- Wall time: ~2-3 min for 5 parallel agents
Quality control
Section titled “Quality control”After bulk-tag, run extended battery (/tmp/query-battery-extended.mts) covering:
- Emotion: smile, sick, sad, crying, surprised, confused, tired
- Gender: boy/girl/man/woman/mixed
- Activity: playing, reading, cooking, studying, exercising
- Object props: ball, brain, lightbulb, card, star, heart, book, house, piano
- Food specifics: pho, burger, salad, rice, egg, chicken, salmon, bread
- VN aliases: be, tre, banh, sua, bep, truong, tay, nha
Aim for ≥95% pass on extended battery. Real failures (vs catalog gaps) are when a query SHOULD match items in current catalog but returns 0 - those need taxonomy synonym additions or per-item tag corrections.
Catalog gaps (NOT tag failures)
Section titled “Catalog gaps (NOT tag failures)”These queries genuinely return 0 because the source catalog has no matching illustration:
sleeping- no character is depicted sleepingdancing- no dance illustrationoffice- no office scene (close =workingcovers this)morning/evening- no time-of-day-specific sceneoutline/3d- applies to icons via FAMILY_VISUAL_TAGS, not illustrations
If any of these is desired, source new illustrations rather than fake-tagging existing ones.
Maintenance
Section titled “Maintenance”- Keep
_meta.coverageaccurate when adding items - helps consumers know what’s tagged. - When the manifest cache regenerates with new illustrations (
figma_executeextraction), the override file persists. Items with stale node ids (deleted from Figma) become harmless dead entries - clean during periodic janitor sweep. - The override file is the source of truth for visual tags. The generator is deterministic - re-running rebuilds the merge. No data lives only in the generated manifest.