Skip to main content

vrt-css-fix-loop

Closed-loop CSS auto-repair. Given a fixture with a known regression (one CSS property or one selector block removed), iterate with a VLM that proposes the missing fix from the diff screenshot, apply it, and re-run until the diff falls below a threshold. Currently scoped to the CSS-challenge fixture set in `src/experiments/css-challenge/`; adapting to an arbitrary repo requires writing a fixture entry. Use when measuring whether a VLM model can recover a known regression, not for production self-healing.

来源信息

仓库
mizchi/vlmkit
最近来源活动
2026年8月4日 04:45
检测到的 SKILL.md 语言
英语
星标
29
分支
3

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
vrt-css-fix-loop
description
Closed-loop CSS auto-repair. Given a fixture with a known regression (one CSS property or one selector block removed), iterate with a VLM that proposes the missing fix from the diff screenshot, apply it, and re-run until the diff falls below a threshold. Currently scoped to the CSS-challenge fixture set in `src/experiments/css-challenge/`; adapting to an arbitrary repo requires writing a fixture entry. Use when measuring whether a VLM model can recover a known regression, not for production self-healing.
metadata
{"internal":true}
# vrt-css-fix-loop `fix-loop` is the harness behind every VLM benchmark in this repo (`docs/reports/2026-05-18-vlm-claude-vs-openrouter-vs-newcomers.md` etc.). It takes a fixture, deliberately mutates the CSS (`property` mode deletes one property; `selector` mode deletes one selector block), then runs a **two-stage** AI pipeline to propose a fix from the rendered diff. **The pipeline is VLM → LLM, not VLM alone:** 1. Apply current candidate CSS to the variant page. 2. Render baseline + variant; compute pixel diff. 3. **Stage 1 (VLM)**: send the diff overlay to the VLM with a structured "list the changes" prompt; parse a CHANGE list (`selector { prop: from → to }` rows). 4. **Stage 2 (LLM)**: hand the CHANGE list + current CSS to an LLM (default `claude-sonnet-*`); LLM emits the actual CSS edits to apply. The LLM compensates for VLM imprecision — it may rewrite, merge, or discard VLM proposals based on the CSS context. 5. Re-run; stop when diffRatio falls below threshold (= FIXED) or `--max-rounds` is exhausted. **FIXED means "the pipeline converged," not "the VLM understood the diff."** It's common to see VLM propose selectors unrelated to the deleted block while the LLM still emits a working fix. When using this harness to *benchmark a VLM model*, compare CHANGE-list quality between models — don't read FIXED as a VLM verdict on its own. The banner line `VLM=<id> | LLM=<id>` makes the two stages explicit. ## Invocation `fix-loop` is run directly from source (not via the `vlmkit` CLI): ```bash node --experimental-strip-types src/experiments/css-challenge/fix-loop.ts <flags> ``` It does not ship in `./dist/vrt.mjs`. The source form above is the only supported entry point. ## When to use - Evaluating a new VLM model on UI-domain understanding. - Comparing two model providers on the same fixture (controlled benchmark). - Understanding "what kind of fix can a VLM actually propose?" before shipping a self-healing feature. ## When NOT to use - Production self-repair on an arbitrary user repo — the harness only knows fixtures registered in `css-challenge-fixtures.ts`. Adapting to a new repo means writing a fixture entry + goal CSS first. - Bulk regression triage: use `vrt-visual-diff` for one-shot reads. - Per-PR CI gate: use `vrt-regression-watch`. ## Quickstart This repo uses direnv; `.envrc` auto-loads `.env.local` (where `OPENROUTER_API_KEY` / `ANTHROPIC_API_KEY` live). If you're shelling outside the direnv context, run `set -a; source .env.local; set +a` first. ```bash # Property mode (default): delete one CSS property; pipeline proposes restoration node --experimental-strip-types src/experiments/css-challenge/fix-loop.ts \ --fixture page --seed 42 # Selector mode: delete one full selector block (harder) node --experimental-strip-types src/experiments/css-challenge/fix-loop.ts \ --fixture page --seed 11 --mode selector --max-rounds 3 # Run with a specific VLM model (any OpenRouter id, `gemini:*`, or `claude:*`). # The Stage-2 LLM is held constant across VLM swaps so VLM quality # can be compared apples-to-apples. VLMKIT_VLM_MODEL="bytedance/ui-tars-1.5-7b" \ node --experimental-strip-types src/experiments/css-challenge/fix-loop.ts \ --fixture page --seed 11 --mode selector ``` ## Available fixtures Listed in `src/experiments/css-challenge/css-challenge-fixtures.ts`. Common entries: | Fixture | Layout | Typical seeds | |---|---|---| | `page` | README-style article + sidebar | 1-99 | | (others registered in the file) | … | … | The seed maps deterministically to "which property / selector got deleted." Seed 11 in selector mode is the canonical hard case (`.readme-body pre` losing 6 properties → 4.1% diffRatio) used in VLM benchmarks. ## VLM model selection The harness honours `VLMKIT_VLM_MODEL`. Prefix selects the provider: | Prefix | Provider | Example | |---|---|---| | (no prefix) | OpenRouter | `bytedance/ui-tars-1.5-7b` | | `gemini:` | Google AI | `gemini:gemini-2.5-flash` | | `claude:` | Anthropic | `claude:claude-haiku-4-5-20251001` | Current recommendations (from `.claude/CLAUDE.md`): - **Default**: `bytedance/ui-tars-1.5-7b` (UI-domain-trained, ~1.4s single-call, $0/call). Verified FIXED in round 1 on the canonical hard case (seed 11, .readme-body pre {6 props}, 4.1% diff → 0.0%). - **Stable / detailed**: `qwen/qwen3-vl-30b-a3b-instruct` (emits hex codes directly). - **Baseline fallback**: `amazon/nova-lite-v1`. - **High coverage + prose root-cause**: `claude:claude-haiku-4-5-20251001` (~4.2s single-call, ~$2e-6/call; also FIXED in round 1 on seed 11 — works as Stage-1 VLM despite format divergence, Stage-2 LLM handles it). Avoid: `meta-llama/llama-4-scout` (regressed; verbose), `meta-llama/llama-4-maverick` (returns "image not available"), `google/gemini-2.5-flash-lite` (hallucinates uniform deltas). See `docs/reports/2026-05-19-vlm-haiku-vs-uitars.md` for the latest 2-way re-bench; `docs/reports/2026-05-18-vlm-claude-vs-openrouter-vs-newcomers.md` for the 8-way bench from the prior week. ## Flags | Flag | Default | Purpose | |---|---|---| | `--fixture <name>` | — | Required. Fixture id from `css-challenge-fixtures.ts` | | `--seed <int>` | — | Required. Seeds the deterministic mutation | | `--mode <property\|selector>` | `property` | Mutation granularity | | `--max-rounds <int>` | 5 | Hard ceiling on iterations | | `--threshold <float>` | 0.001 | diffRatio at which FIXED is declared | | `--no-db` | off | Skip writing the benchmark DB row | ## Environment | Variable | Required when | |---|---| | `VLMKIT_VLM_MODEL` | Always (defaults if unset). Provider auto-detected from prefix | | `OPENROUTER_API_KEY` | Unprefixed model id | | `GEMINI_API_KEY` | `gemini:` prefix | | `ANTHROPIC_API_KEY` | `claude:` prefix | | `DEBUG_VLMKIT=1` | Verbose VLM round logging | ## Reading the output Real banner + first round of a real run on seed 11 selector mode: ``` VLM=bytedance/ui-tars-1.5-7b | LLM=claude-sonnet-4-20250514 Removed block: .readme-body pre { 6 props } Round 1: VLM: 5 changes (3383ms) .main { padding: 16px → 24px } .sidebar { width: 100% → 296px } .header-nav { display: none → flex } .header-search{ max-width: none → 320px } .tabs { padding: 0 16px → 0 24px } LLM: 6 fixes proposed diff: 4.12% → 0.00% (FIXED ✓) ``` Things to read: - `VLM=<id> | LLM=<id>` — confirms which two models drove the pipeline. - **`VLM: N changes (Tms)`** — VLM stage's wall-clock + the CHANGE list. Per-row format `selector { prop: from → to }`. - **`LLM: M fixes proposed`** — Stage-2 emitted M CSS edits; usually M ≥ N (LLM expands / corrects). - `diff: x% → y%` — diffRatio before this round's edits vs after. - `FIXED ✓` when `y` falls below `--threshold` (default `0.001`). **Pipeline divergence warning**: if `Removed block:` names something the VLM never mentions in its CHANGE list, *but the pipeline still hits FIXED*, the LLM compensated. Treat that run as a **win for the pipeline**, not for the VLM. To grade the VLM in isolation, score the CHANGE list against the known-removed block (e.g. selector recall, property recall). A "stalled" run shows diffRatio holding steady across rounds — the VLM's proposals aren't parseable, or the LLM's emitted fixes aren't structurally valid CSS. Set `DEBUG_VLMKIT=1` to see both stages' raw output. At the end of the run, a summary table prints one row per round with columns: | Column | Meaning | |---|---| | `Round` | 1-indexed iteration number | | `Diff` | diffRatio after this round's edits applied | | `Changes` | rows in VLM's CHANGE list (Stage 1) | | `Fixes` | CSS edits emitted by LLM (Stage 2). Usually `Fixes ≥ Changes` | | `Escalated` | `false` if the default LLM tier handled it; `true` if the harness fell back to a higher-capability model. A run with `Escalated=true` cost more — relevant for cross-model benchmarks. | ## Adapting to a new repo The harness is fixture-bound — to run on a user repo: 1. Add a fixture entry in `css-challenge-fixtures.ts` describing the page (HTML + goal CSS + variant CSS template). 2. Confirm baseline + variant render the same when seed maps to a no-op (sanity check). 3. Run the loop. If the new repo is large enough that fixture-style isolation isn't viable, this skill is the wrong tool — use `vrt-visual-diff` to surface the regression and edit by hand. ## Costs (rough) Per call, based on the 2026-05-18 bench: - `bytedance/ui-tars-1.5-7b`: ~$0.1e-6 / $0.2e-6 (input / output). - `claude:claude-haiku-4-5-*`: ~$0.002 / call. Budget consideration: a 3-round fix-loop on Haiku ≈ $0.006 / run; on ui-tars-1.5-7b ≈ negligible. For batch benchmark runs (>100 calls), prefer the OpenRouter models.
在 GitHub 查看