Skip to content

Tag Overrides Workflow - Illustrations

bydesign-systemUpdated

How to add per-item visual tags to illustrations so semantic search (happy, running, book, burger) returns the right items.

Build-time generator tools/build-plugin-library-data.mjs auto-tags each illustration by category only:

SectionCategory 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.

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)
  • _name is optional documentation - ignored by the generator, helpful for humans diffing
  • tags is the only field read; merged with rule-tags then deduped and capped at 12 total per item

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 student just because the figure looks studious - too inferential

Cover up to 7 dimensions for character illustrations (V0.2.2 expanded):

  1. Subject - boy / girl / kid / friends / group / teacher
  2. Emotion - happy / sad / surprised / confused / angry / excited / sick
  3. Pose (body position) - standing / sitting / running / waving / holding
  4. Activity (V0.2.2; verb form, what-is-happening) - cooking / reading / sleeping / dancing / exercising / working / shopping / driving / painting
  5. Object held / context - pencil / book / piano / helmet / ball / lightbulb
  6. Setting (V0.2.2; where) - school / office / home / outdoor / kitchen / library / beach / city
  7. 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):

  1. Specific name - burger / egg / tomato / medal / brain / armchair / coin
  2. Family - vegetable / pastry / award / spice / furniture / currency
  3. Function / activity (V0.2.2) - cooking / seasoning / hydration / payment
  4. Setting (V0.2.2; if scene-like) - kitchen / library / office
  5. 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 → query wow/amazed/shock finds the item (taxonomy expansion)
  • Tag confused → query worried/puzzled/wondering finds it
  • Tag book → query reading/study/textbook finds it

So you don’t need to bake every variation into the override - pick one canonical and the search engine bridges the rest.

  1. Open the illustration thumbnail at studio/public/ds-assets/illustrations/<section>/<id-with-dash>.png.
  2. 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).
  3. Draft 3–7 tags per the dimensions above.
  4. Add to tag-overrides-illustrations.json:
    "<nodeId>": { "_name": "<name>", "tags": [...] }
  5. Run node tools/build-plugin-library-data.mjs - re-emits the manifest + plugin generated data.
  6. (Optional) node tools/build-plugin-library-data.mjs --check confirms outputs are committed up-to-date for CI.
  7. Spot-check by searching the new tag in Studio web (localhost:4000/design-system/#illustrations) or the Figma plugin panel.

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 eggs items - could add eggs to breakfast.synonyms so breakfast query 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. classcourse AND group) caused book queries to match friends items in early prototyping. See header comment in tag-taxonomy.ts buildSynonymExpander().

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.

  • 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)
  1. 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
    "
  2. 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)
  3. 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: true for parallel execution
  4. 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 + lastUpdated
    require('fs').writeFileSync('design-guideline/figma-ai/tag-overrides-illustrations.json', JSON.stringify(overrides, null, 2) + '\n');
    "
  5. Verify + deploy:

    • node tools/build-plugin-library-data.mjs → regenerates manifest + plugin gen
    • Run query battery (/tmp/query-battery.mts or your own) - expect ≥85% pass
    • npm test in packages/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)

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

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.

These queries genuinely return 0 because the source catalog has no matching illustration:

  • sleeping - no character is depicted sleeping
  • dancing - no dance illustration
  • office - no office scene (close = working covers this)
  • morning/evening - no time-of-day-specific scene
  • outline/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.

  • Keep _meta.coverage accurate when adding items - helps consumers know what’s tagged.
  • When the manifest cache regenerates with new illustrations (figma_execute extraction), 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.