From e713eaa37aecf3ec89bc249a33f3d6a09cc03834 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 14 Mar 2026 16:18:27 +0300 Subject: [PATCH] Remove autoresearch scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keeping the actual fixes (instance overrides, constraint scaling, renderer improvements) — only removing experiment infrastructure. --- autoresearch-compare.ts | 125 ---------------------------------------- autoresearch.checks.sh | 4 -- autoresearch.ideas.md | 20 ------- autoresearch.jsonl | 12 ---- autoresearch.md | 59 ------------------- autoresearch.sh | 10 ---- 6 files changed, 230 deletions(-) delete mode 100644 autoresearch-compare.ts delete mode 100755 autoresearch.checks.sh delete mode 100644 autoresearch.ideas.md delete mode 100644 autoresearch.jsonl delete mode 100644 autoresearch.md delete mode 100755 autoresearch.sh diff --git a/autoresearch-compare.ts b/autoresearch-compare.ts deleted file mode 100644 index 808f13b3a..000000000 --- a/autoresearch-compare.ts +++ /dev/null @@ -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() - -function collect(proxy: ReturnType, parentPath: string, sibIdx: number, sibNames: Map) { - // 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 })._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() - for (let i = 0; i < children.length; i++) { - collect(children[i] as ReturnType, path, i, childNames) - } - } -} - -const pageChildren = (api.wrapNode(page.id) as unknown as { children?: unknown[] }).children ?? [] -const topNames = new Map() -for (let i = 0; i < pageChildren.length; i++) { - collect(pageChildren[i] as ReturnType, '', 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}`) diff --git a/autoresearch.checks.sh b/autoresearch.checks.sh deleted file mode 100755 index 10dbda1b8..000000000 --- a/autoresearch.checks.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -euo pipefail -bun run test:unit 2>&1 | tail -5 -bun run lint 2>&1 | tail -3 diff --git a/autoresearch.ideas.md b/autoresearch.ideas.md deleted file mode 100644 index 464ae0044..000000000 --- a/autoresearch.ideas.md +++ /dev/null @@ -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 diff --git a/autoresearch.jsonl b/autoresearch.jsonl deleted file mode 100644 index fe5aaaa73..000000000 --- a/autoresearch.jsonl +++ /dev/null @@ -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} diff --git a/autoresearch.md b/autoresearch.md deleted file mode 100644 index 201e820a3..000000000 --- a/autoresearch.md +++ /dev/null @@ -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. diff --git a/autoresearch.sh b/autoresearch.sh deleted file mode 100755 index 1b29a3879..000000000 --- a/autoresearch.sh +++ /dev/null @@ -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