Remove autoresearch scaffolding

Keeping the actual fixes (instance overrides, constraint scaling,
renderer improvements) — only removing experiment infrastructure.
This commit is contained in:
Danila Poyarkov 2026-03-14 16:18:27 +03:00
parent 152b0d1666
commit e713eaa37a
6 changed files with 0 additions and 230 deletions

View file

@ -1,125 +0,0 @@
/**
* Compare .fig import against Figma ground truth.
*
* Node IDs differ between .fig import and Figma Plugin API, so we match
* nodes by tree path: sequence of (name, sibling_index) from root to leaf.
*
* Truth: tests/fixtures/gold-preview-truth.json (extracted from live Figma).
*/
import { readFigFile } from './packages/core/src/kiwi/fig-file'
import { FigmaAPI } from './packages/core/src/figma-api'
const fixturePath = process.argv[2] || 'tests/fixtures/gold-preview.fig'
const truthPath = 'tests/fixtures/gold-preview-truth.json'
interface TruthNode {
path: string
name: string
type: string
visible: boolean
width: number
height: number
text?: string
fill?: string
cr?: number
clip?: boolean
}
function colorHex(c: { r: number; g: number; b: number; a: number }): string {
const r = Math.round(c.r * 255)
const g = Math.round(c.g * 255)
const b = Math.round(c.b * 255)
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
}
const truth: TruthNode[] = JSON.parse(await Bun.file(truthPath).text())
const truthMap = new Map(truth.map(n => [n.path, n]))
const file = Bun.file(fixturePath)
const graph = await readFigFile(new File([await file.arrayBuffer()], 'gold-preview.fig'))
const api = new FigmaAPI(graph)
const page = api.root.children[0]
if (!page) { console.error('No page found'); process.exit(1) }
const ourNodes = new Map<string, TruthNode>()
function collect(proxy: ReturnType<FigmaAPI['wrapNode']>, parentPath: string, sibIdx: number, sibNames: Map<string, number>) {
// Use name-based index when all sibling names are unique, numeric index otherwise
const nameCount = sibNames.get(proxy.name) ?? 0
sibNames.set(proxy.name, nameCount + 1)
const path = `${parentPath}/${proxy.name}[${nameCount}]`
const raw = (proxy as unknown as { _raw(): Record<string, unknown> })._raw()
const fills = raw.fills as Array<{ type: string; color: { r: number; g: number; b: number; a: number }; visible: boolean }> | undefined
const visibleFill = fills?.find(f => f.type === 'SOLID' && f.visible !== false)
const entry: TruthNode = {
path,
name: proxy.name,
type: proxy.type,
visible: (proxy as unknown as { visible: boolean }).visible,
width: Math.round((raw.width as number) * 100) / 100,
height: Math.round((raw.height as number) * 100) / 100,
}
const chars = (proxy as unknown as { characters?: string }).characters
if (proxy.type === 'TEXT' && chars) entry.text = chars
if (visibleFill) entry.fill = colorHex(visibleFill.color)
let cr = raw.cornerRadius as number
if (raw.independentCorners) {
const tl = raw.topLeftRadius as number ?? 0
const tr = raw.topRightRadius as number ?? 0
const br = raw.bottomRightRadius as number ?? 0
const bl = raw.bottomLeftRadius as number ?? 0
if (tl === tr && tr === br && br === bl) cr = tl
else cr = Math.max(tl, tr, br, bl)
}
if (cr > 0) entry.cr = Math.round(cr * 10) / 10
if (raw.clipsContent) entry.clip = true
ourNodes.set(path, entry)
const children = (proxy as unknown as { children?: unknown[] }).children
if (children) {
const childNames = new Map<string, number>()
for (let i = 0; i < children.length; i++) {
collect(children[i] as ReturnType<FigmaAPI['wrapNode']>, path, i, childNames)
}
}
}
const pageChildren = (api.wrapNode(page.id) as unknown as { children?: unknown[] }).children ?? []
const topNames = new Map<string, number>()
for (let i = 0; i < pageChildren.length; i++) {
collect(pageChildren[i] as ReturnType<FigmaAPI['wrapNode']>, '', i, topNames)
}
let diffs = 0, visibility = 0, text = 0, fills = 0, radius = 0, size = 0, matched = 0
for (const [path, t] of truthMap) {
const o = ourNodes.get(path)
if (!o) continue
matched++
if (o.visible !== t.visible) { visibility++; diffs++ }
if (t.text && o.text !== t.text) { text++; diffs++ }
if (t.fill && o.fill && t.fill.toLowerCase() !== o.fill.toLowerCase()) { fills++; diffs++ }
if (t.cr && t.cr > 0) {
const oCr = o.cr ?? 0
// Both >= half the smaller dimension = fully rounded (pill), treat as equal
const minDim = Math.min(t.width, t.height)
const bothPill = oCr >= minDim / 2 && t.cr >= minDim / 2
if (!bothPill && Math.abs(oCr - t.cr) > 1) { radius++; diffs++ }
}
if (t.visible && o.visible && (Math.abs(o.width - t.width) > 1 || Math.abs(o.height - t.height) > 1)) {
size++; diffs++
}
}
const unmatched = truth.length - matched
console.log(`METRIC total_diffs=${diffs}`)
console.log(`METRIC visibility=${visibility}`)
console.log(`METRIC text=${text}`)
console.log(`METRIC fills=${fills}`)
console.log(`METRIC radius=${radius}`)
console.log(`METRIC size=${size}`)
console.log(`METRIC unmatched=${unmatched}`)

View file

@ -1,4 +0,0 @@
#!/bin/bash
set -euo pipefail
bun run test:unit 2>&1 | tail -5
bun run lint 2>&1 | tail -3

View file

@ -1,20 +0,0 @@
# Autoresearch Ideas
## High Priority (11 fill diffs)
- **Library variable alias resolution**: ALL remaining fill diffs are caused by `colorVar` on symbolOverride paints. The `paint.color` contains the fallback value but the `colorVar.value.alias.assetRef` references an EXTERNAL library variable. Local copy exists but with different GUID. Need to build mapping from `assetRef.key + version` → local variable ID using `variableConsumptionMap` entries on NodeChanges. This would fix all 11 fill diffs at once.
## Medium Priority (50 size diffs)
- **Badge area layout (~25 nodes)**: Badge INSTANCE width 96→85.32 is a Yoga layout issue. Auto-layout computes width from children sizes which depend on text measurement. Could improve by applying DSD size overrides to the Badge shell directly (if DSD entries exist but aren't resolving).
- **Datepicker width (7 nodes)**: `_datepicker-date-range-link` width 32→131. FIXED sizing but Figma shows wider — check if `layoutGrow` or parent `FILL` sizing should expand it.
- **Remaining Vector scaling (16 nodes)**: Inside Badge area. Would cascade-fix if Badge width is corrected.
## Attempted & Reverted
- **kiwiPropertyNodes in seeds + preserveFills**: Adding kiwiPropertyNodes as BFS extra seeds allows propagation of kiwi NC values to clones, but the skip=seeds coupling causes visibility regressions. Need to decouple "BFS start nodes" from "don't overwrite" in propagateOverridesTransitively — a structural refactor of the sync function's skip logic.
## Investigated / Won't Fix In This Session
- Avatar distortion: Fixed by skipping auto-layout instances in constraint scaling
- 99 unmatched nodes: Fixed by name-based tree path matching
- cornerRadius 999 vs 890: Fixed by pill-shape tolerance
- Bold toolbar button fill: Fixed by self-referencing symbolOverride skip
- Icon cropping: Fixed by DSD single-child fallback + SCALE constraints
- Text measurement diffs (~10 nodes): Font-dependent, sub-2px differences

View file

@ -1,12 +0,0 @@
{"type":"config","name":"Figma .fig import fidelity","metricName":"total_diffs","metricUnit":"","bestDirection":"lower"}
{"run":1,"commit":"8a90594","metric":246,"metrics":{"visibility":1,"text":0,"fills":10,"radius":161,"size":74,"unmatched":99},"status":"keep","description":"Baseline: 246 diffs (1 vis, 0 text, 10 fills, 161 radius, 74 size, 99 unmatched)","timestamp":1773482910363,"segment":0}
{"run":2,"commit":"3746d1a","metric":99,"metrics":{"visibility":1,"text":0,"fills":10,"radius":14,"size":74,"unmatched":99},"status":"keep","description":"Fix comparison: read individual corner radii when independentCorners=true","timestamp":1773483304162,"segment":0}
{"run":3,"commit":"7fc3a64","metric":85,"metrics":{"visibility":1,"text":0,"fills":10,"radius":0,"size":74,"unmatched":99},"status":"keep","description":"Fix comparison: treat both-pill cornerRadius values as equal","timestamp":1773483389254,"segment":0}
{"run":4,"commit":"2858d94","metric":84,"metrics":{"visibility":1,"text":0,"fills":9,"radius":0,"size":74,"unmatched":99},"status":"keep","description":"Skip self-referencing symbolOverrides on nodes with explicit kiwi properties (fixes bold toolbar button fill)","timestamp":1773484182748,"segment":0}
{"run":5,"commit":"fd5b0b5","metric":106,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":94,"unmatched":0},"status":"keep","description":"Name-based tree path matching: 0 unmatched (was 99), reveals 106 total real diffs","timestamp":1773488934943,"segment":0}
{"run":6,"commit":"97ba675","metric":70,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":58,"unmatched":0},"status":"keep","description":"Apply SCALE constraint resizing to instance children + propagate through clone chains","timestamp":1773489529498,"segment":0}
{"run":7,"commit":"4f3df83","metric":62,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":50,"unmatched":0},"status":"keep","description":"Skip SCALE constraint resizing for auto-layout instances (fixes Avatar distortion)","timestamp":1773490511259,"segment":0}
{"run":8,"commit":"919e4cf","metric":62,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":50,"unmatched":0},"status":"discard","description":"Analysis only: remaining 11 fills are all library variable aliases, 50 sizes are layout-dependent","timestamp":1773491467403,"segment":0}
{"run":9,"commit":"4f3df83","metric":62,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":50,"unmatched":0},"status":"discard","description":"Reverted kiwiPropertyNodes/extraSeeds experiments — too complex, caused regressions","timestamp":1773493300107,"segment":0}
{"run":10,"commit":"b6a1d91","metric":62,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":50,"unmatched":0},"status":"discard","description":"Reverted reapplyKiwiProperties — can't distinguish sync-overwritten from symbolOverride-set fills","timestamp":1773493747060,"segment":0}
{"run":11,"commit":"d513312","metric":62,"metrics":{"visibility":1,"text":0,"fills":11,"radius":0,"size":50,"unmatched":0},"status":"keep","description":"Narrow kiwiPropertyNodes: only flag nodes whose fills/radius/visible DIFFER from component source","timestamp":1773494252125,"segment":0}

View file

@ -1,59 +0,0 @@
# Autoresearch: Figma .fig Import Fidelity
## Objective
Minimize the number of node property differences between our .fig file import
and the ground truth from Figma's Plugin API. The benchmark compares visibility,
text content, fill colors, corner radius, and node dimensions on the Preview page
of the Preline UI design system.
## Metrics
- **Primary**: `total_diffs` (count, lower is better) — sum of all property mismatches
- **Secondary**: `visibility`, `text`, `fills`, `radius`, `size`, `unmatched`
## How to Run
```
./autoresearch.sh
```
Outputs `METRIC name=number` lines. Uses `tests/fixtures/gold-preview.fig` as input
and `tests/fixtures/gold-preview-truth.json` as ground truth (extracted from live Figma).
## Files in Scope
- `packages/core/src/kiwi/instance-overrides/` — override resolution pipeline (types, index, populate, resolve, symbol-overrides, sync, props, dsd)
- `packages/core/src/kiwi/kiwi-convert.ts` — main kiwi-to-SceneNode converter
- `packages/core/src/kiwi/kiwi-convert-overrides.ts` — symbolOverride field conversion
- `packages/core/src/kiwi/codec.ts` — NodeChange type definitions
- `packages/core/src/scene-graph.ts` — SceneNode type and defaults
- `packages/core/src/renderer/scene.ts` — render pipeline (clipping, effects)
- `packages/core/src/renderer/shapes.ts` — makeRRect, shape helpers
- `autoresearch-compare.ts` — comparison script (may refine matching logic)
## Off Limits
- `packages/core/src/kiwi/kiwi-schema/` — vendored kiwi codec, do not modify
- Test fixtures (.fig files) — read-only
- Anything in `src/` (app code) — this is a core import fidelity issue
## Constraints
- All 914+ engine tests must pass (`bun run test:unit`)
- Zero lint errors (`bun run lint`)
- No behavior regressions — fixes should be additive
## What's Been Tried
- **Instance swap overrides**: Fixed propagation through clone chains (ef0b45f)
- **Component property defaults**: Empty kiwi value `{}` → reset to initialValue (2cecdff)
- **Text overrides clobbered by second sync**: Added `protect` set (72ec3ee)
- **DSD for swapped instances**: Single-child fallback in resolveOverrideTarget (d93c475)
- **Rounded clipping**: clipRRect when clipsContent + cornerRadius (49423e4)
- **Shadow child shape**: Drop shadow on transparent containers follows first child (f70338d)
- **Current best**: 62 total_diffs (1 vis, 0 text, 11 fills, 0 radius, 50 size, 0 unmatched) — 74.8% improvement
- **kiwiPropertyNodes narrowed**: Only nodes whose fills/radius/visible actually differ from component source (was too broad — any NC with fillPaints)
- **reapplyKiwiProperties failed**: Can't distinguish sync-overwritten from symbolOverride-set fills. Reverted.
- **1 visibility diff root cause**: Email TEXT 0:3480 stays visible because its source 0:3455 IS in seeds (kiwiPropertyNodes) but 0:3452→0:3477 parent sync never runs (0:3477 gets visited by a different sync path first). Needs BFS/skip decoupling.
- **Comparison fixes**: independentCorners radius reading, pill-shape tolerance, name-based tree matching
- **kiwiPropertyNodes**: Nodes with explicit kiwi NC fills/cornerRadius are added to seeds AND protected from sync overwrite
- **Self-referencing symbolOverride**: When an override resolves to the instance itself, skip if the instance has explicit kiwi NC properties
- **SCALE constraint resizing**: Apply proportional scaling to children when instance size ≠ component size; skip auto-layout instances; propagate through clone chains
- **Remaining fills (11)**: Variable-bound colors (3 Indicators, 1 Ellipse), deep chain color overrides (3 badge Placeholders, 1 Link, 1 Vector, 1 button Placeholder, 1 Left Divider)
- **Remaining sizes (50)**: 16 Vectors (Badge 1.12x ratio), 7 datepicker widths, 3+3+3 Badge/Avatar/Placeholder sizing (layout-dependent), 4 Groups, misc text widths
- **Root causes of remaining diffs**:
- **Fills (11)**: ALL fill overrides use `colorVar` library aliases. The `paint.color` is the fallback value, but the alias resolves to a different color in context. Need library asset ref → local variable mapping via `variableConsumptionMap`.
- **Sizes (50)**: 30+ are Badge area (~1.12x ratio from layout-dependent width), 7 datepicker (layout), rest are text measurement diffs. These need pixel-perfect text measurement or DSD propagation improvements.

View file

@ -1,10 +0,0 @@
#!/bin/bash
set -euo pipefail
# Compare gold-preview.fig import against Figma Plugin API ground truth.
# Outputs METRIC lines for the autoresearch dashboard.
FIXTURE=tests/fixtures/gold-preview.fig
# Run the comparison script
bun run autoresearch-compare.ts "$FIXTURE" 2>/dev/null