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
آخر نشاط في المصدر
٤ أغسطس ٢٠٢٦ في ٠٤:٤٥
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٩
التفرعات
٣

خيارات التثبيت

يُحدَّد 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