<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Tsisyk</title>
    <description>The latest articles on DEV Community by Roman Tsisyk (@romantsisyk).</description>
    <link>https://dev.to/romantsisyk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4070290%2Fe23ad162-cd2f-4be7-a741-fcc6ed8b6698.jpg</url>
      <title>DEV Community: Roman Tsisyk</title>
      <link>https://dev.to/romantsisyk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/romantsisyk"/>
    <language>en</language>
    <item>
      <title>EAGLE-4 does not run on a Mac. I measured MTP instead: +92% on one model, -19% on another</title>
      <dc:creator>Roman Tsisyk</dc:creator>
      <pubDate>Wed, 21 Oct 2026 21:24:28 +0000</pubDate>
      <link>https://dev.to/romantsisyk/eagle-4-does-not-run-on-a-mac-i-measured-mtp-instead-92-on-one-model-19-on-another-5g9a</link>
      <guid>https://dev.to/romantsisyk/eagle-4-does-not-run-on-a-mac-i-measured-mtp-instead-92-on-one-model-19-on-another-5g9a</guid>
      <description>&lt;p&gt;A release announcement for EAGLE-4, a speculative decoding method, reported up to 3.4x faster generation for Qwen3.8-27B on four NVIDIA B300 GPUs. I run local models on a Mac Studio, and I wanted to know one thing: does it make my models faster?&lt;/p&gt;

&lt;p&gt;The short answer: EAGLE-4 as released does not run on a Mac. But my runtime already has a related technique, MTP (multi-token prediction), and I can measure that. The result depends strongly on the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a dense 27B model, MTP made code generation 92% faster (thinking off, one request).&lt;/li&gt;
&lt;li&gt;On a 35B mixture-of-experts (MoE) model, MTP made prose generation 19% slower (thinking off, one request).&lt;/li&gt;
&lt;li&gt;With MTP on, the output text changed at temperature 0 in 7 of 8 test cases. I did not measure quality yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post covers speed only. All numbers below come from my own runs, except the EAGLE-4 numbers, which come from the EAGLE-4 README.&lt;/p&gt;

&lt;h2&gt;
  
  
  What EAGLE-4 is, and why I did not test it
&lt;/h2&gt;

&lt;p&gt;Speculative decoding works like this. A small, fast drafter proposes several next tokens. The large model checks all of them in one forward pass and keeps the tokens that match its own choice. If most drafts are correct, you get several tokens for the cost of about one large-model pass.&lt;/p&gt;

&lt;p&gt;EAGLE-4 is a trained drafter for this job. Facts from the official repository (&lt;a href="https://github.com/eagle-hq/eagle-4" rel="noopener noreferrer"&gt;github.com/eagle-hq/eagle-4&lt;/a&gt;, package &lt;code&gt;eagle-harness&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The drafter is &lt;code&gt;EAGLE-Corp/Qwen3.8-27B-EAGLE4&lt;/code&gt;: 3B parameters, BF16.&lt;/li&gt;
&lt;li&gt;It runs inside SGLang, vLLM and TokenSpeed (experimental).&lt;/li&gt;
&lt;li&gt;The quick start requires Linux, four NVIDIA GPUs with a CUDA 13 driver and about 70 GB of disk. A separate guide covers AMD Instinct MI355X.&lt;/li&gt;
&lt;li&gt;The README does not mention Metal, MLX, llama.cpp or Apple Silicon.&lt;/li&gt;
&lt;li&gt;The license is source-available, not OSI-approved. Read &lt;code&gt;LICENSE&lt;/code&gt; and &lt;code&gt;NOTICE&lt;/code&gt; in the repository for the terms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The README reports these speedups for Qwen3.8-27B at temperature 0. Each value is EAGLE-4 throughput divided by the same engine without speculation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine and hardware&lt;/th&gt;
&lt;th&gt;1 request&lt;/th&gt;
&lt;th&gt;8 concurrent&lt;/th&gt;
&lt;th&gt;32 concurrent&lt;/th&gt;
&lt;th&gt;64 concurrent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SGLang, 4x B300&lt;/td&gt;
&lt;td&gt;3.40x&lt;/td&gt;
&lt;td&gt;1.91x&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;1.35x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vLLM, 4x B300&lt;/td&gt;
&lt;td&gt;2.99x&lt;/td&gt;
&lt;td&gt;1.70x&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;1.16x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TokenSpeed (experimental)&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;2.02x&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;1.42x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SGLang, 1x AMD MI355X&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;2.07x&lt;/td&gt;
&lt;td&gt;1.56x&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes. First, the gain falls as concurrency rises, as expected: a fuller batch leaves less idle compute for verification. Second, a news post quoted "3.59x vs vanilla MTP". I did not find that figure in the README. The README baseline is the same engine without speculation, not MTP.&lt;/p&gt;

&lt;p&gt;Pointers to related work on Apple Silicon (I did not verify these results):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An EAGLE-3 prototype for MLX: &lt;a href="https://github.com/ml-explore/mlx-lm/discussions/890" rel="noopener noreferrer"&gt;mlx-lm discussion #890&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;MTPLX (&lt;a href="https://github.com/youssofal/MTPLX" rel="noopener noreferrer"&gt;github.com/youssofal/MTPLX&lt;/a&gt;) and dflash-mlx (&lt;a href="https://github.com/bstnxbt/dflash-mlx" rel="noopener noreferrer"&gt;github.com/bstnxbt/dflash-mlx&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;A post on aiweekly.co (&lt;a href="https://aiweekly.co/alerts/mtp-hurts-apple-silicon-llm-speed-up-to-25" rel="noopener noreferrer"&gt;aiweekly.co&lt;/a&gt;) says MTP makes M1 to M3 chips up to 25% slower. This report is anecdotal, and other reports disagree with it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I found no EAGLE-4 result on a Mac or on MLX anywhere. So I tested the technique that I can run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tested
&lt;/h2&gt;

&lt;p&gt;Some new models ship with an MTP head: a small extra layer, trained with the model, that predicts tokens further ahead. A runtime can use this head as the drafter. You do not need a separate draft model. My runtime, oMLX, calls this feature "Lightning MTP" and turns it on per model with &lt;code&gt;mtp_enabled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardware and software&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mac Studio, Apple M2 Ultra (60 GPU cores), 64 GB unified memory, macOS 26.5.&lt;/li&gt;
&lt;li&gt;oMLX 0.7.1rc1, an OpenAI-compatible server for Apple Silicon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Loaded size&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B oQ4e-mtp (Jundot)&lt;/td&gt;
&lt;td&gt;Dense, 27B&lt;/td&gt;
&lt;td&gt;16.2 GB&lt;/td&gt;
&lt;td&gt;Hybrid Gated DeltaNet and full attention, 64 layers, mixed 4/5-bit, 4.887 bits/weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ornith-1.5-35B-A3B oQ4e-mtp (LookUpMark)&lt;/td&gt;
&lt;td&gt;MoE, 35B total&lt;/td&gt;
&lt;td&gt;20.5 GB&lt;/td&gt;
&lt;td&gt;256 experts, 8 per token, about 3B active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-4B-Instruct-2507 4-bit (mlx-community)&lt;/td&gt;
&lt;td&gt;Dense, 4B&lt;/td&gt;
&lt;td&gt;2.3 GB&lt;/td&gt;
&lt;td&gt;Control, no MTP head&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Method&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One model per server process. A fresh server for each phase, in the order OFF, ON, OFF, ON.&lt;/li&gt;
&lt;li&gt;Temperature 0, streaming, one request at a time unless stated.&lt;/li&gt;
&lt;li&gt;Prefix cache off (&lt;code&gt;--no-cache&lt;/code&gt;), oMLX memory guard off.&lt;/li&gt;
&lt;li&gt;Two prompts: a Kotlin coding task and a 350-word prose explanation. Each with thinking on and thinking off.&lt;/li&gt;
&lt;li&gt;10 runs per prompt per phase, 512 max tokens. Warm-up runs to compile kernels are not counted.&lt;/li&gt;
&lt;li&gt;Decode tok/s = completion tokens / (time of last streamed chunk − time of first chunk). This excludes time to first token.&lt;/li&gt;
&lt;li&gt;Prefill = the server's &lt;code&gt;prompt_eval_duration&lt;/code&gt; for Python standard-library source code as the prompt: 2,037, 3,913, 7,875 and 16,125 tokens, 5 runs each.&lt;/li&gt;
&lt;li&gt;A sampler recorded GPU use, memory, swap and thermal state every 2 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Limits of this test&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One machine, one runtime version, community quantizations of each model.&lt;/li&gt;
&lt;li&gt;Temperature 0 only. Sampling at higher temperatures can lower draft acceptance.&lt;/li&gt;
&lt;li&gt;Background processes (a browser, Activity Monitor, a code-indexing tool) used CPU during parts of the runs.&lt;/li&gt;
&lt;li&gt;Qwen MTP-off phases agree within 1.4%. Ornith has more spread: a standard deviation of up to 3.9 tok/s with MTP off (one phase with four slow runs excluded, see below) and up to 10.5 tok/s with MTP on.&lt;/li&gt;
&lt;li&gt;Prefill, long-context and concurrency tests ran in one phase only, with 3 to 5 runs each.&lt;/li&gt;
&lt;li&gt;Swap stayed at or below 100 MB for the whole run, the CPU speed limit stayed at 100%, and the server logged zero "Prefill throttled" lines.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Decode speed
&lt;/h3&gt;

&lt;p&gt;Values are mean tok/s over 20 runs (2 phases × 10 runs). "Parked" counts runs in which oMLX paused MTP and used normal decoding for 128 tokens (log line: "parked for 128 standard tokens"). I did not check the exact rule that oMLX uses to decide this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;MTP off&lt;/th&gt;
&lt;th&gt;MTP on&lt;/th&gt;
&lt;th&gt;On / off&lt;/th&gt;
&lt;th&gt;Parked runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B (dense)&lt;/td&gt;
&lt;td&gt;Code, thinking off&lt;/td&gt;
&lt;td&gt;33.6&lt;/td&gt;
&lt;td&gt;64.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.92x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking off&lt;/td&gt;
&lt;td&gt;33.7&lt;/td&gt;
&lt;td&gt;41.6&lt;/td&gt;
&lt;td&gt;1.23x&lt;/td&gt;
&lt;td&gt;1 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Code, thinking on&lt;/td&gt;
&lt;td&gt;33.5&lt;/td&gt;
&lt;td&gt;46.7&lt;/td&gt;
&lt;td&gt;1.39x&lt;/td&gt;
&lt;td&gt;1 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking on&lt;/td&gt;
&lt;td&gt;33.6&lt;/td&gt;
&lt;td&gt;33.9&lt;/td&gt;
&lt;td&gt;1.01x&lt;/td&gt;
&lt;td&gt;20 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ornith-1.5-35B-A3B (MoE)&lt;/td&gt;
&lt;td&gt;Code, thinking off&lt;/td&gt;
&lt;td&gt;132.5&lt;/td&gt;
&lt;td&gt;147.9&lt;/td&gt;
&lt;td&gt;1.12x&lt;/td&gt;
&lt;td&gt;0 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking off&lt;/td&gt;
&lt;td&gt;133.1&lt;/td&gt;
&lt;td&gt;107.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.81x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Code, thinking on&lt;/td&gt;
&lt;td&gt;132.7&lt;/td&gt;
&lt;td&gt;144.8&lt;/td&gt;
&lt;td&gt;1.09x&lt;/td&gt;
&lt;td&gt;0 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking on&lt;/td&gt;
&lt;td&gt;118.0*&lt;/td&gt;
&lt;td&gt;109.8&lt;/td&gt;
&lt;td&gt;0.93x*&lt;/td&gt;
&lt;td&gt;0 / 20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* In the second MTP-off phase, four runs dropped to 38 to 112 tok/s. During those runs, the sampler shows a background code-indexing process at high CPU use and GPU use falling well below normal. With medians, this cell is 132.7 off and 111.4 on, a ratio of &lt;strong&gt;0.84x&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Three things stand out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the dense model, MTP helps on every task but one. The gain is largest on code without thinking.&lt;/li&gt;
&lt;li&gt;On the MoE model, MTP helps a little on code and hurts on prose.&lt;/li&gt;
&lt;li&gt;Parking did not protect Ornith. It parked MTP in all 20 Qwen prose-with-thinking runs, which kept that case at +1%. It never parked MTP in Ornith's 512-token runs, even when they were 19% slower.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The control model, Qwen3-4B, ran at 159.2 to 159.9 tok/s in all four scenarios. (It has no thinking mode, and its output was the same with thinking on and off.) Ornith, with 35B total parameters, decodes at 83% of the speed of this 4B dense model. With MTP off, Ornith decodes 3.95x faster than Qwen3.8-27B on a single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefill
&lt;/h3&gt;

&lt;p&gt;Prefill is the time to read the prompt before the first token. Values are server-side, with MTP off.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt tokens&lt;/th&gt;
&lt;th&gt;Qwen3.8-27B time&lt;/th&gt;
&lt;th&gt;Qwen3.8-27B tok/s&lt;/th&gt;
&lt;th&gt;Ornith time&lt;/th&gt;
&lt;th&gt;Ornith tok/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2,037&lt;/td&gt;
&lt;td&gt;8.0 s&lt;/td&gt;
&lt;td&gt;253&lt;/td&gt;
&lt;td&gt;1.2 s&lt;/td&gt;
&lt;td&gt;1,771&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3,913&lt;/td&gt;
&lt;td&gt;16.6 s&lt;/td&gt;
&lt;td&gt;236&lt;/td&gt;
&lt;td&gt;2.3 s&lt;/td&gt;
&lt;td&gt;1,679&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7,875&lt;/td&gt;
&lt;td&gt;33.8 s&lt;/td&gt;
&lt;td&gt;233&lt;/td&gt;
&lt;td&gt;4.8 s&lt;/td&gt;
&lt;td&gt;1,641&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16,125&lt;/td&gt;
&lt;td&gt;75.2 s&lt;/td&gt;
&lt;td&gt;215&lt;/td&gt;
&lt;td&gt;11.9 s&lt;/td&gt;
&lt;td&gt;1,358&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With 16,125 prompt tokens, Qwen3.8-27B waits 75.2 seconds before the first token. Ornith waits 11.9 seconds. MTP does not help prefill. With MTP on, prefill was 1.7% to 6.2% slower for Qwen and 2.5% to 3.7% slower for Ornith.&lt;/p&gt;

&lt;h3&gt;
  
  
  Long context and concurrency
&lt;/h3&gt;

&lt;p&gt;These tests used thinking off. Concurrency requests alternate between the code prompt and the prose prompt. Aggregate tok/s = all completion tokens / wall time, so it includes time to first token.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Qwen off&lt;/th&gt;
&lt;th&gt;Qwen on&lt;/th&gt;
&lt;th&gt;On / off&lt;/th&gt;
&lt;th&gt;Ornith off&lt;/th&gt;
&lt;th&gt;Ornith on&lt;/th&gt;
&lt;th&gt;On / off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Long context: 7,878-token prompt, 300 tokens out (5 runs)&lt;/td&gt;
&lt;td&gt;32.0&lt;/td&gt;
&lt;td&gt;37.5&lt;/td&gt;
&lt;td&gt;1.17x&lt;/td&gt;
&lt;td&gt;124.3&lt;/td&gt;
&lt;td&gt;110.5&lt;/td&gt;
&lt;td&gt;0.89x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 request, decode only (reference from the first table)&lt;/td&gt;
&lt;td&gt;33.6&lt;/td&gt;
&lt;td&gt;52.9&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;td&gt;132.8&lt;/td&gt;
&lt;td&gt;127.7&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 concurrent requests, aggregate (3 runs)&lt;/td&gt;
&lt;td&gt;43.3&lt;/td&gt;
&lt;td&gt;66.7&lt;/td&gt;
&lt;td&gt;1.54x&lt;/td&gt;
&lt;td&gt;158.4&lt;/td&gt;
&lt;td&gt;174.1&lt;/td&gt;
&lt;td&gt;1.10x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 concurrent requests, aggregate (3 runs)&lt;/td&gt;
&lt;td&gt;49.5&lt;/td&gt;
&lt;td&gt;85.6&lt;/td&gt;
&lt;td&gt;1.73x&lt;/td&gt;
&lt;td&gt;217.5&lt;/td&gt;
&lt;td&gt;199.9&lt;/td&gt;
&lt;td&gt;0.92x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1-request row is a different metric (decode only, the mean of code and prose). I show it only for scale. On the dense model, MTP still helps with four requests in parallel. On Ornith with the long prompt, oMLX parked MTP in all 5 runs, and the result was still 11% slower than MTP off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the dense and MoE models differ
&lt;/h2&gt;

&lt;p&gt;This section is my explanation of the data. The numbers come from oMLX's own MTP log lines. The cause is an inference that I did not prove with a profiler.&lt;/p&gt;

&lt;p&gt;Each MTP cycle drafts several tokens and verifies them in one pass of the main model. Two numbers decide the result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokens per cycle&lt;/strong&gt;: how many tokens one cycle produces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cycle cost&lt;/strong&gt;: how long one cycle takes, in units of one normal decode step with MTP off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The speedup is about tokens per cycle divided by cycle cost. This is accounting, not a model, but it shows which factor matters. Values below come from runs that were not parked.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Accept rate&lt;/th&gt;
&lt;th&gt;Tokens per cycle&lt;/th&gt;
&lt;th&gt;Cycle cost (normal steps)&lt;/th&gt;
&lt;th&gt;Measured on / off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3.8-27B&lt;/td&gt;
&lt;td&gt;Code, thinking off&lt;/td&gt;
&lt;td&gt;93.4%&lt;/td&gt;
&lt;td&gt;4.20&lt;/td&gt;
&lt;td&gt;2.16&lt;/td&gt;
&lt;td&gt;1.92x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking off&lt;/td&gt;
&lt;td&gt;69.6%&lt;/td&gt;
&lt;td&gt;2.64&lt;/td&gt;
&lt;td&gt;2.11&lt;/td&gt;
&lt;td&gt;1.23x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Code, thinking on&lt;/td&gt;
&lt;td&gt;77.5%&lt;/td&gt;
&lt;td&gt;3.09&lt;/td&gt;
&lt;td&gt;2.17&lt;/td&gt;
&lt;td&gt;1.39x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ornith-1.5-35B-A3B&lt;/td&gt;
&lt;td&gt;Code, thinking off&lt;/td&gt;
&lt;td&gt;85.2%&lt;/td&gt;
&lt;td&gt;3.12&lt;/td&gt;
&lt;td&gt;2.73&lt;/td&gt;
&lt;td&gt;1.12x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking off&lt;/td&gt;
&lt;td&gt;63.7%&lt;/td&gt;
&lt;td&gt;2.13&lt;/td&gt;
&lt;td&gt;2.58&lt;/td&gt;
&lt;td&gt;0.81x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Code, thinking on&lt;/td&gt;
&lt;td&gt;83.0%&lt;/td&gt;
&lt;td&gt;3.05&lt;/td&gt;
&lt;td&gt;2.73&lt;/td&gt;
&lt;td&gt;1.09x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Prose, thinking on&lt;/td&gt;
&lt;td&gt;65.7%&lt;/td&gt;
&lt;td&gt;2.18&lt;/td&gt;
&lt;td&gt;2.58&lt;/td&gt;
&lt;td&gt;0.84x (medians)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Qwen prose with thinking on is not in the table, because MTP was parked in every run.&lt;/p&gt;

&lt;p&gt;The acceptance rates are similar. Ornith on code accepted 85% of drafts, more than Qwen on code with thinking on (78%). The big difference is cycle cost. A Qwen cycle costs about 2.1 to 2.2 normal steps. An Ornith cycle costs about 2.6 to 2.7. So Ornith needs about 2.6 tokens per cycle only to break even, and on prose it produces about 2.1.&lt;/p&gt;

&lt;p&gt;Why is verification more expensive for the MoE model? My explanation: in a dense model, all tokens in the verification pass use the same weights, so one read of the weights from memory serves several tokens. In an MoE model, each token selects 8 of 256 experts, and different tokens select different experts. Verifying four or five tokens therefore reads many more expert weights than one normal step does. Also, a normal Ornith step is short (7.5 ms against 29.8 ms for Qwen), so any fixed cost per cycle is a larger share. In the logs, the main model's pass takes 92% of Ornith's cycle time and 96% of Qwen's. I did not profile the kernels, so treat this as a hypothesis. It can also be a property of the oMLX implementation and not of MoE models in general.&lt;/p&gt;

&lt;h3&gt;
  
  
  MTP changed the output text
&lt;/h3&gt;

&lt;p&gt;Speculative decoding with greedy verification is supposed to produce exactly the text the model produces alone. In my runs, it did not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With MTP off, every scenario produced one identical text across all 20 runs.&lt;/li&gt;
&lt;li&gt;With MTP on, the output matched the MTP-off text in only one scenario: Qwen code with thinking off (20 of 20 runs).&lt;/li&gt;
&lt;li&gt;In the other seven scenarios, 0 of 20 runs matched. The first difference appeared between character 26 and character 423, depending on the scenario, and the texts continued differently after it. The MTP-on texts also differed from each other between runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the samples I read, the differences are changes of wording, not obvious errors. But I did not measure quality, so I cannot say if they matter. One possible cause is small numerical differences between the multi-token verification pass and the normal single-token pass. Such differences can flip a near-tie between two tokens. I did not prove this. For the speed numbers, it means that MTP on and MTP off produced texts of the same length (almost all runs reached the 512-token limit), but not the same texts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaways
&lt;/h2&gt;

&lt;p&gt;These apply to these models on oMLX 0.7.1rc1 and an M2 Ultra:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Turn MTP on for Qwen3.8-27B on code.&lt;/strong&gt; It gave +92% with thinking off and +39% with thinking on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave MTP off for Ornith-1.5-35B-A3B by default.&lt;/strong&gt; It gave +9% to +12% on code, but −7% to −19% on prose, −11% with a long prompt and −8% with four parallel requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure each model.&lt;/strong&gt; Dense or MoE mattered more for the result than any headline number for the technique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For agents, prefill matters as much as decode.&lt;/strong&gt; The dense model needs 75 seconds to read a 16k-token prompt, and MTP makes prefill slightly slower. With MTP off, Ornith reads the same prompt in 12 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check output quality before you rely on MTP.&lt;/strong&gt; It changed the text in 7 of 8 scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Quality of any output, with MTP on or off. Ornith's 4x decode speed says nothing about whether its code is as good as Qwen's.&lt;/li&gt;
&lt;li&gt;Temperatures above 0.&lt;/li&gt;
&lt;li&gt;Other runtimes (llama.cpp, mlx-lm directly, LM Studio) and other drafters (EAGLE-3, DFlash).&lt;/li&gt;
&lt;li&gt;Other Apple chips. An M1, M3 or M4 has different bandwidth and compute, and the balance can change.&lt;/li&gt;
&lt;li&gt;Prompts longer than 16k tokens, and real agent sessions with tool calls.&lt;/li&gt;
&lt;li&gt;Energy use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The next test is quality. I will use real commits from my open-source Kotlin Multiplatform app, MyEuroStatApp, as tasks. I will compare what these local models produce, with MTP on and off, against the output of my usual coding-agent workflow on the same commits. I do not have results yet, and I do not know what they will show.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The scripts are here: &lt;a href="https://github.com/RomanTsisyk/mtp-bench-m2ultra" rel="noopener noreferrer"&gt;github.com/RomanTsisyk/mtp-bench-m2ultra&lt;/a&gt;. The repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;suite.py&lt;/code&gt;: the benchmark. It starts one oMLX server per phase, runs the warm-up, decode, prefill, long-context and concurrency tests, and writes &lt;code&gt;summary.json&lt;/code&gt;, &lt;code&gt;outputs.jsonl&lt;/code&gt; and &lt;code&gt;server.log&lt;/code&gt; for each phase.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sampler.py&lt;/code&gt;: records GPU use, memory, swap, CPU speed limit and the top processes every 2 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run_all.sh&lt;/code&gt;: runs the full plan in the order I used.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_numbers.py&lt;/code&gt;: recomputes every number in this post from the raw files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To repeat the test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install oMLX 0.7.1rc1 and download the three models from Hugging Face.&lt;/li&gt;
&lt;li&gt;Close other heavy applications. Background CPU load affected some of my runs.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;./run_all.sh&lt;/code&gt;. The full plan took about 1 hour 43 minutes on my machine.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;python3 -I verify_numbers.py &amp;lt;results folder&amp;gt;&lt;/code&gt; and compare its output with the tables above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run it on a different Mac, I want to see your numbers, especially for MoE models on M3 and M4 chips.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>macos</category>
      <category>performance</category>
    </item>
    <item>
      <title>Claude Code + Figma: A Deterministic Design Handoff Pipeline</title>
      <dc:creator>Roman Tsisyk</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:47:36 +0000</pubDate>
      <link>https://dev.to/romantsisyk/claude-code-figma-a-deterministic-design-handoff-pipeline-lk3</link>
      <guid>https://dev.to/romantsisyk/claude-code-figma-a-deterministic-design-handoff-pipeline-lk3</guid>
      <description>&lt;p&gt;Screenshot prompting has a ceiling. You paste the design, the model makes a plausible approximation, you correct it, and on the next turn it drifts again. Nothing is anchored. The model has no source of truth to check itself against between turns.&lt;/p&gt;

&lt;p&gt;A context bundle changes the contract. Instead of a pixel reference the model has to interpret every time, you get a structured, referenceable set of files — design tokens, layout IR, component inventory, UI strings — that stay in the session and stay consistent. Claude Code can read them, implement from them, and check its own output against them on demand. This post walks the full pipeline, from bundle export to a reviewed, token-verified implementation, using &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope&lt;/a&gt;, a browser tool that turns any Figma file into exactly that bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes this deterministic
&lt;/h2&gt;

&lt;p&gt;Three things make the bundle referenceable rather than interpretable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tokens are typed and keyed.&lt;/strong&gt; &lt;code&gt;tokens.json&lt;/code&gt; maps semantic names (&lt;code&gt;spacing.16&lt;/code&gt;, &lt;code&gt;color.7f5cfe&lt;/code&gt;) to exact values. The model can check its output against the file without re-processing the design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The IR is a tree, not pixels.&lt;/strong&gt; &lt;code&gt;screens/home.json&lt;/code&gt; describes the layout in terms of stack/overlay/absolute/leaf nodes — the same abstraction the implementation target (Compose, React, etc.) uses. There's no visual interpretation step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bundle is stable across turns.&lt;/strong&gt; Once it's in the repo, every prompt in the session can reference the same files. Token drift is detectable: ask the model to compare its output against &lt;code&gt;tokens.json&lt;/code&gt; and it can do it mechanically.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Generate the bundle
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope.dev&lt;/a&gt; in your browser. Paste your Figma file URL. The exporter runs client-side using the Figma REST API — your Figma personal access token is stored in &lt;code&gt;localStorage&lt;/code&gt; and never sent to figmascope's servers.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Export Agent Context&lt;/strong&gt;. The page exports top-level frames, resolves design tokens, builds the IR, and downloads &lt;code&gt;context-bundle.zip&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Unzip into your project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# from your project root&lt;/span&gt;
unzip ~/Downloads/context-bundle.zip &lt;span class="nt"&gt;-d&lt;/span&gt; ./design/

&lt;span class="c"&gt;# confirm what you have&lt;/span&gt;
find design/ &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;sort&lt;/span&gt;
&lt;span class="c"&gt;# design/CONTEXT.md&lt;/span&gt;
&lt;span class="c"&gt;# design/_meta.json&lt;/span&gt;
&lt;span class="c"&gt;# design/components/inventory.json&lt;/span&gt;
&lt;span class="c"&gt;# design/screens/home.json&lt;/span&gt;
&lt;span class="c"&gt;# design/screens/home.png&lt;/span&gt;
&lt;span class="c"&gt;# design/screens/settings.json&lt;/span&gt;
&lt;span class="c"&gt;# design/screens/settings.png&lt;/span&gt;
&lt;span class="c"&gt;# design/strings.json&lt;/span&gt;
&lt;span class="c"&gt;# design/tokens.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory name doesn't matter — &lt;code&gt;design/&lt;/code&gt; is just a convention. What matters is that Claude Code can read the files from the working directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Start Claude Code in your repo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code starts in your repo root with full file access. It can read, write, and reference any file in the tree across the entire session — this is the key capability that makes the bundle pattern work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Orient the agent
&lt;/h2&gt;

&lt;p&gt;Start with a reading prompt before any implementation. This loads the spec into the session context and lets you verify the export looks right before writing any code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read ./design/CONTEXT.md and tell me:
1. What target framework is this bundle for?
2. What token files does it reference?
3. Are there any warnings I should know about before implementing?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude will report back the target (Jetpack Compose by default), the token source, and any warnings from the CONTEXT.md header — gradient fills, missing token mappings, unsupported effects. You catch these now, not after generating 200 lines of code.&lt;/p&gt;

&lt;p&gt;Follow up with a quick token check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List the top 10 color tokens from ./design/tokens.json.
Then list the spacing tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms the token file parsed correctly and gives you a mental model of the palette before implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Implement a screen
&lt;/h2&gt;

&lt;p&gt;Now the implementation prompt. Be explicit about which files are authoritative for which decisions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement ./design/screens/home.json as a Jetpack Compose screen.

Rules:
- CONTEXT.md constraints apply. Read it if you haven't already.
- All spacing, color, and radius values must come from ./design/tokens.json.
  Map token keys to the appropriate Compose primitives (e.g. spacing.16 → 16.dp).
- UI strings must use keys from ./design/strings.json via stringResource().
  Fall back to the "fallback" field value if no resource ID is available yet.
- The IR node kinds map as follows:
    stack (axis:vertical)   → Column
    stack (axis:horizontal) → Row
    overlay                 → Box
    absolute                → Box with Modifier.offset
    leaf (text)             → Text with TextStyle
    leaf (rectangle)        → Box with Modifier.background
- Do not invent any value not present in the token or IR files.
  If something is missing, leave a TODO comment with the token key you expected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code will read the IR, walk the node tree, map each node to its Compose primitive, and pull token values by key. The output is traceable: every &lt;code&gt;.dp&lt;/code&gt; value should correspond to a spacing token, every &lt;code&gt;Color(0xFF...)&lt;/code&gt; should match a color token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Detect and fix token drift
&lt;/h2&gt;

&lt;p&gt;After the first implementation pass, run a drift check before reviewing visually. This is the key advantage of the bundle over screenshot prompting — you can ask the model to verify its own output mechanically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compare every color value in the generated HomeScreen.kt against ./design/tokens.json.
List any hex values in the output that don't correspond to a color token key.
For each one, identify the correct token and replace the hardcoded value.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do the same for spacing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compare every .dp value in HomeScreen.kt against the spacing tokens in ./design/tokens.json.
Flag any value that doesn't match a spacing token. Replace with the correct token reference.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loop — implement, check drift, fix — converges fast. By the second or third pass, the output is fully token-referenced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tip: include &lt;code&gt;_meta.json&lt;/code&gt; warnings in your first prompt
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;design/_meta.json&lt;/code&gt; contains a &lt;code&gt;warnings&lt;/code&gt; array. These are things the exporter couldn't fully resolve: gradient fills, embedded images, effects without a token equivalent. Read them before implementing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;design/_meta.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the output includes something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"node 'hero-background': gradientFill not fully supported — background fill omitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"node 'avatar': imageFill — reference only, no pixel data"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add these explicitly to your implementation prompt: "Skip the hero background fill and leave a &lt;code&gt;// TODO: gradient&lt;/code&gt;. For the avatar node, use a placeholder &lt;code&gt;AsyncImage&lt;/code&gt; with a grey background."&lt;/p&gt;

&lt;p&gt;This prevents Claude from silently approximating — it'll do what you told it instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats screenshot prompting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multi-turn safe.&lt;/strong&gt; The token file and IR don't change between turns. You can ask "did you use the right spacing for the card padding?" in turn 12 and get an accurate answer, because the source of truth is still on disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diff-friendly.&lt;/strong&gt; When you re-export after a design change, the new bundle produces a diff against the old one. You can ask Claude to review the diff and update only the affected components — no full reimplementation required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No re-upload.&lt;/strong&gt; The bundle lives in your repo. You don't re-paste screenshots for every new screen. Each new screen is just &lt;code&gt;design/screens/&amp;lt;name&amp;gt;.json&lt;/code&gt; — one more file to reference in the next prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest about gaps.&lt;/strong&gt; CONTEXT.md and &lt;code&gt;_meta.json&lt;/code&gt; explicitly list what the bundle doesn't cover. Screenshot prompting has no equivalent — the model just guesses through the gaps.&lt;/p&gt;

&lt;p&gt;The main &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope app&lt;/a&gt; handles the export in your browser — paste your Figma URL, export the bundle, and you're ready to run Claude Code against a deterministic spec. The same bundle pattern also works with &lt;a href="https://figmascope.dev/blog/cursor-figma-workflow" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; and &lt;a href="https://figmascope.dev/blog/aider-figma-workflow" rel="noopener noreferrer"&gt;Aider&lt;/a&gt; if you want to compare agent outputs on identical input.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>figma</category>
      <category>ai</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Structured Context vs Pixel Context: What Coding Agents Actually Need</title>
      <dc:creator>Roman Tsisyk</dc:creator>
      <pubDate>Sun, 09 Aug 2026 23:00:43 +0000</pubDate>
      <link>https://dev.to/romantsisyk/structured-context-vs-pixel-context-what-coding-agents-actually-need-4fl8</link>
      <guid>https://dev.to/romantsisyk/structured-context-vs-pixel-context-what-coding-agents-actually-need-4fl8</guid>
      <description>&lt;p&gt;Context is becoming the bottleneck in AI-assisted development. Not model capability — models are improving fast enough that they're regularly not the constraint. What limits the quality of AI-generated code is the quality of context those models receive.&lt;/p&gt;

&lt;p&gt;For Figma-to-code workflows, context comes in two fundamentally different forms: &lt;strong&gt;pixel context&lt;/strong&gt; (screenshots, rendered images) and &lt;strong&gt;structured context&lt;/strong&gt; (typed IR, tokens, semantic relationships). These aren't just different formats for the same information. They're different categories of input, with different properties, different loss characteristics, and different ceilings on what an agent can produce from them.&lt;/p&gt;

&lt;p&gt;The industry is still largely using pixel context. That's a mistake. &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope&lt;/a&gt; exports structured context — the right input from the start. Here's how the two categories compare, and why the difference determines whether your generated code composes, diffs, and survives contact with a real design system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pixel context is
&lt;/h2&gt;

&lt;p&gt;Pixel context is any rasterized representation of a design: a screenshot exported from Figma, a PNG from "Export frame", a render from a design tool. It's what you get when you press Cmd+Shift+4 over your Figma canvas.&lt;/p&gt;

&lt;p&gt;Vision-capable LLMs process pixel context impressively well. They recognize UI patterns, identify layout regions, infer component types from visual appearance, and generate plausible code from images alone. If you've used Claude or GPT-4V for screenshot-to-code, you've seen this. The outputs look right more often than you'd expect.&lt;/p&gt;

&lt;p&gt;But "looks right" and "is right" are not the same thing — and the gap between them is where design system compliance, token fidelity, component identity, and reproducibility all live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What structured context is
&lt;/h2&gt;

&lt;p&gt;Structured context is a typed, machine-readable representation that preserves the semantics of the design: what each element &lt;em&gt;is&lt;/em&gt;, not just what it &lt;em&gt;looks like&lt;/em&gt;. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed nodes:&lt;/strong&gt; every element has a kind (&lt;code&gt;FRAME&lt;/code&gt;, &lt;code&gt;TEXT&lt;/code&gt;, &lt;code&gt;INSTANCE&lt;/code&gt;, &lt;code&gt;VECTOR&lt;/code&gt;) that carries semantic meaning about its role in the layout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Named values:&lt;/strong&gt; colors are token references, not hex strings; spacings are token keys, not pixel values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spatial relationships:&lt;/strong&gt; layout direction, gap, padding, alignment — preserved as properties, not inferred from position&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity links:&lt;/strong&gt; component instances carry their source component ID; strings carry cross-reference keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchy:&lt;/strong&gt; the full node tree, with parent-child relationships intact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A structured IR is the design tree made explicit: each node has &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;absoluteBoundingBox&lt;/code&gt;, &lt;code&gt;children&lt;/code&gt;, fills resolved to token references where available, auto-layout properties if applicable, and &lt;code&gt;componentId&lt;/code&gt; on instances.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pixel context tells an agent what a design looks like. Structured context tells it what a design means. A coding agent needs meaning to write code, not appearance. Appearance is what visual tests are for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What gets lost in the pixel-to-structured direction
&lt;/h2&gt;

&lt;p&gt;The core failure mode of pixel context is irreversible information loss. When Figma renders a frame to PNG, it discards exactly the information that matters most for code generation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The layer tree collapses.&lt;/strong&gt; There is no longer a "group of three items with 8px gaps." There is a region of pixels that suggests a group. The agent has to reconstruct the tree structure from visual evidence, and reconstruction is approximate. It will be wrong some percentage of the time — and that percentage grows as designs get more complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token bindings disappear.&lt;/strong&gt; The orange background that maps to &lt;code&gt;color/action/primary&lt;/code&gt; becomes &lt;code&gt;#FF6B00&lt;/code&gt;. The agent generates a hardcoded hex. If your color ever changes, or you support dark mode, or you need to audit token usage, that hardcoded value is a liability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component identity is gone.&lt;/strong&gt; Four instances of the same card component are four similar-looking rectangles. The agent may generate one reusable component or four similar-but-not-identical blocks, depending on how much structural similarity it infers. You want predictable output; you get probabilistic output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layout intent is ambiguous.&lt;/strong&gt; Is this a flex row or a grid? Is the spacing between items a gap, a margin, or padding on each item? The pixels don't say. The agent picks — and the picks differ between runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Figma → React pipeline, with and without structure
&lt;/h2&gt;

&lt;p&gt;Consider the path from Figma to production React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With pixel context:&lt;/strong&gt; Export PNG. Paste into Claude. Get JSX. Review JSX for correctness. Notice hardcoded values. Notice wrong component structure. Prompt for corrections. Iterate. Eventually get something plausible. Hand-edit to match the design system. Ship. Next screen: repeat from scratch because the previous run's outputs don't compose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With structured context:&lt;/strong&gt; Export a bundle (one click, runs in browser). Pass CONTEXT.md + screen IR to Claude with a system prompt specifying framework and design system conventions. Get JSX that uses your token names, your component names, and correct layout structure. Review for correctness. Ship. Next screen: same bundle, same agent, composable outputs because the inputs are consistent.&lt;/p&gt;

&lt;p&gt;The work savings are real but secondary. The primary gain is composability. Structured context enables outputs that compose across screens and agents. Pixel context doesn't — each screen's output is an island generated from a fresh inference pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure means: typed
&lt;/h2&gt;

&lt;p&gt;Every node in the IR has a &lt;code&gt;kind&lt;/code&gt;. This matters immediately. A &lt;code&gt;TEXT&lt;/code&gt; node generates a text element. A &lt;code&gt;FRAME&lt;/code&gt; with auto-layout generates a container. An &lt;code&gt;INSTANCE&lt;/code&gt; of &lt;code&gt;Button/Primary/Large&lt;/code&gt; generates a button component call with the right props. A &lt;code&gt;VECTOR&lt;/code&gt; generates an icon reference.&lt;/p&gt;

&lt;p&gt;The agent doesn't guess. It maps kinds to code primitives — rules specified in CONTEXT.md for the target framework: "For INSTANCE nodes, use the component name to determine the React component. For FRAME with layoutMode HORIZONTAL, use a flex row. For TEXT with style typography/heading.lg, use the Heading component." These are compiler-style rules, not inference tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure means: spatial
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;absoluteBoundingBox&lt;/code&gt; on each node gives position and size in the Figma coordinate space. Combined with auto-layout properties — &lt;code&gt;layoutMode&lt;/code&gt;, &lt;code&gt;itemSpacing&lt;/code&gt;, &lt;code&gt;paddingLeft/Right/Top/Bottom&lt;/code&gt;, alignment — the agent has everything it needs to generate correct layout code without pixel-counting.&lt;/p&gt;

&lt;p&gt;The bounding boxes also let the agent verify its own output: if a generated component has different dimensions than the IR specified, something went wrong. That's a testable property of structured context with no equivalent in pixel context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure means: identity-aware
&lt;/h2&gt;

&lt;p&gt;When four nodes in the IR share a &lt;code&gt;componentId&lt;/code&gt;, the agent knows they're instances of the same component. It generates the component definition once, derives props from the variants, and renders four calls. This is the correct output — and it's not achievable from pixel context without significant prompt engineering that essentially asks the agent to re-derive structure the design file already had.&lt;/p&gt;

&lt;p&gt;String cross-references work the same way. When multiple text nodes reference &lt;code&gt;stringRef.key: "action.continue"&lt;/code&gt;, the agent knows to use a single i18n lookup, not three hardcoded strings. The identity information is in the IR; the agent just reads it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure means: version-controllable
&lt;/h2&gt;

&lt;p&gt;Plain JSON files diff cleanly. A changed padding value shows up as a one-line change in the per-screen IR. A renamed token shows up as a find-replace diff across the tokens file. A new component instance shows up as an added object in the children array.&lt;/p&gt;

&lt;p&gt;This is design version history that's actually useful for engineers. Not "the design was updated on Tuesday" but "here are the three properties that changed between the v2 and v3 exports of this screen." You can put that in your PR description, run automated checks on it, and audit whether the code change matches the design change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leads: design context infrastructure
&lt;/h2&gt;

&lt;p&gt;The tooling category forming here isn't "Figma export, but better." It's a new layer in the stack: &lt;strong&gt;design context infrastructure&lt;/strong&gt;. Its job is to transform design source (Figma files, component libraries, token systems) into structured, agent-readable, version-controlled artifacts that feed the code generation layer.&lt;/p&gt;

&lt;p&gt;This layer sits between the design tool and the coding agent, with responsibilities neither side currently owns: snapshot management, semantic extraction, token resolution, component inventory, cross-screen string indexing, bundle versioning. Treating it as infrastructure means it's automated, versioned, CI-runnable, defined-format, and inspectable — the same way a build system is infrastructure for code: not the code, not the binary, but the reliable, reproducible pipeline that converts one to the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest: pixels still matter
&lt;/h2&gt;

&lt;p&gt;Structured context bundles include 2x PNGs of every exported screen. Not because the PNG drives code generation, but because visual confirmation matters. An agent should be able to cross-reference its generated output against the PNG. A developer should be able to look at the screen without opening Figma. The PNG is a sanity check, not a specification.&lt;/p&gt;

&lt;p&gt;That distinction — pixels for confirmation, structure for specification — is the right mental model. You don't eliminate pixel context; you demote it to its correct role. It's the QA artifact, not the build input.&lt;/p&gt;

&lt;p&gt;The same way you wouldn't give a compiler a screenshot of your source code: you give it the source, and you use screenshots for documentation. The design file is the source. The bundle is the compilation artifact. The PNG is the documentation image.&lt;/p&gt;

&lt;h2&gt;
  
  
  One design, multiple targets
&lt;/h2&gt;

&lt;p&gt;Structured context enables a workflow pixel context can't: one design, multiple targets. The same IR can feed a React/Tailwind generator, a Jetpack Compose generator, and a SwiftUI generator. The underlying design is the same; the target-specific context (framework primitives, naming conventions, layout APIs) lives in CONTEXT.md, which is generated per-target.&lt;/p&gt;

&lt;p&gt;This is multi-target codegen that actually scales. You export one bundle from the design, run three agents with three different CONTEXT.md files, and get three implementations that are structurally equivalent — because they were generated from the same IR, not from three separate inference passes over three screenshots.&lt;/p&gt;

&lt;p&gt;The bottleneck for this workflow isn't model capability. It's context quality. Structured context is what makes it possible.&lt;/p&gt;

&lt;p&gt;Export your structured context bundle from the &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope app&lt;/a&gt;, then use it with &lt;a href="https://figmascope.dev/blog/cursor-figma-workflow" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, &lt;a href="https://figmascope.dev/blog/claude-code-figma-workflow" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, or &lt;a href="https://figmascope.dev/blog/aider-figma-workflow" rel="noopener noreferrer"&gt;Aider&lt;/a&gt; for multi-target, composable UI generation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>design</category>
      <category>figma</category>
    </item>
    <item>
      <title>Why Pasting Figma Screenshots into Claude Fails (And What to Use Instead)</title>
      <dc:creator>Roman Tsisyk</dc:creator>
      <pubDate>Sun, 09 Aug 2026 22:27:56 +0000</pubDate>
      <link>https://dev.to/romantsisyk/why-pasting-figma-screenshots-into-claude-fails-and-what-to-use-instead-46d0</link>
      <guid>https://dev.to/romantsisyk/why-pasting-figma-screenshots-into-claude-fails-and-what-to-use-instead-46d0</guid>
      <description>&lt;p&gt;Here's the workflow that's become default in every design-to-code team right now: export a frame from Figma, paste the PNG into Claude or Cursor, type "build this", and iterate on the hallucinated output. It works just well enough to feel productive. It doesn't work well enough to ship from.&lt;/p&gt;

&lt;p&gt;This isn't a model capability problem. It's an input problem. A screenshot is the worst possible representation of a Figma design for an LLM to reason about — and yet it's almost universally what teams reach for first. The structured alternative is what &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope&lt;/a&gt; exports: a context bundle of typed IR, design tokens, component inventory, and UI strings that the agent can read instead of guess. Let's walk through everything a screenshot silently destroys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hierarchy is gone
&lt;/h2&gt;

&lt;p&gt;A Figma file is a tree. Frames contain auto-layout groups, which contain component instances, which contain text and fill layers. That tree encodes the layout intent: this row is a flex container, this card is a padded box, these three items are siblings with 16px gaps between them.&lt;/p&gt;

&lt;p&gt;A screenshot flattens that tree to a grid of pixels. The LLM sees shapes and colors. It does not see the layout structure — it infers it. And inference is lossy in both directions: the model may reconstruct structure that looks right visually but is wrong semantically (a fixed-width div instead of a flex child, absolute positioning instead of auto-layout), or it may see structural ambiguity and pick one arbitrarily.&lt;/p&gt;

&lt;p&gt;You can't tell from a PNG whether a horizontal row of items is implemented with &lt;code&gt;display: flex&lt;/code&gt;, CSS Grid, a custom HStack, or three absolutely-positioned divs. They're visually identical. The LLM picks one. The pick changes between runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantics don't survive rasterization
&lt;/h2&gt;

&lt;p&gt;The LLM can see that a rectangle with rounded corners contains some text and an icon. What it can't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a &lt;code&gt;Button&lt;/code&gt; component or a custom card?&lt;/li&gt;
&lt;li&gt;If it's a button, what variant is it — primary, secondary, ghost?&lt;/li&gt;
&lt;li&gt;Is the icon decorative or meaningful?&lt;/li&gt;
&lt;li&gt;Does this element have interactive states in the design system, or is it one-off?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semantics in Figma live in the layer tree: component names, variant properties, node kinds. A &lt;code&gt;Button/Primary/Large&lt;/code&gt; component is explicitly typed. In a screenshot, it's a rounded rectangle with a shadow and a label. The model guesses "this is probably a button" correctly most of the time — and then guesses "this is probably the primary variant" based on color, which may or may not match your design system's actual naming.&lt;/p&gt;

&lt;p&gt;Small mismatches compound. A ghost button rendered as an outlined button. A tooltip rendered as a modal trigger. A disabled state rendered as active. Each of these is one screenshot-inference step away from the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spacing systems don't resolve to numbers
&lt;/h2&gt;

&lt;p&gt;Look at a screenshot of a card with padding. What's the padding? You can't tell without measuring pixels, knowing the canvas scale, knowing the export resolution, and doing the math. The LLM does the math badly — it estimates, it rounds, and it has no way to know if your spacing system uses an 8px base grid or a 4px one or something custom.&lt;/p&gt;

&lt;p&gt;So it guesses. It generates &lt;code&gt;padding: 12px&lt;/code&gt; when the design says 16. It generates &lt;code&gt;gap: 8px&lt;/code&gt; when the design says 12. These numbers look plausible in isolation but they're wrong — and if your design system uses spacing tokens like &lt;code&gt;spacing.md&lt;/code&gt; or &lt;code&gt;Spacing/400&lt;/code&gt;, the LLM doesn't know about them at all. It hardcodes literals that will drift from your system the moment anything changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The LLM isn't hallucinating. It's doing exactly what you'd do with only a screenshot: guessing. You're just surprised when the guesses are wrong because you could see the right answer in the Figma file all along.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Token relationships vanish
&lt;/h2&gt;

&lt;p&gt;Your designer set that background to &lt;code&gt;#7F5CFE&lt;/code&gt;. In Figma, that hex is bound to a variable: &lt;code&gt;color/brand/primary&lt;/code&gt;. That binding is meaningful — it means the color participates in theming, it means dark mode swaps it, it means if the brand color changes you update one variable and every instance updates.&lt;/p&gt;

&lt;p&gt;In the screenshot: it's purple. The LLM generates &lt;code&gt;background-color: #7F5CFE&lt;/code&gt;. The token relationship is gone. Your codebase now has a hardcoded hex that will never track with your design system. Multiply this by every component on the screen.&lt;/p&gt;

&lt;p&gt;The same applies to typography scales, border radii, and shadow definitions. Every value in a well-maintained Figma file is potentially a named token. Every value in a screenshot is just a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component reuse is invisible
&lt;/h2&gt;

&lt;p&gt;A well-composed screen reuses components. The four product cards are four instances of the same &lt;code&gt;ProductCard&lt;/code&gt; component. The avatar in the nav and the avatar in the comment thread are both instances of &lt;code&gt;Avatar/Medium&lt;/code&gt;. This matters for code: you want one React component, not four hand-rolled variations that will diverge.&lt;/p&gt;

&lt;p&gt;From a screenshot, the LLM sees four visually similar rectangles. It may generate one reusable component — or it may generate four nearly-identical blocks of JSX because it didn't notice they were the same. There's no signal in the image to tell it which is correct.&lt;/p&gt;

&lt;p&gt;Structured context carries &lt;code&gt;componentId&lt;/code&gt; on every instance node. The agent knows: these four nodes are all &lt;code&gt;ProductCard&lt;/code&gt;. Generate it once, render it four times with different props. That's the output you want. That's the output you can't get from pixels.&lt;/p&gt;

&lt;h2&gt;
  
  
  String identity is lost
&lt;/h2&gt;

&lt;p&gt;You have a "Continue" button on three different screens. Are those three instances the same string, or did a designer write them independently? In a well-structured Figma file, they reference the same string key. That means one &lt;code&gt;i18n&lt;/code&gt; entry, one change propagates everywhere.&lt;/p&gt;

&lt;p&gt;In three screenshots: three times the LLM generates a hardcoded string. If you're building an internationalised app, you now have three strings to find and replace instead of one to look up. Small thing. Compounds across a real codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the LLM hallucinates: it re-derives structure every time
&lt;/h2&gt;

&lt;p&gt;The model has no memory of previous runs. Every time you paste the same screenshot, it reconstructs the structure from scratch. The reconstruction is probabilistic — which means the same screenshot + same prompt + same model can produce measurably different outputs on different runs. Same design, different code. Different component names, different className patterns, different layout choices.&lt;/p&gt;

&lt;p&gt;This is not a model bug. It's the expected behavior of a probabilistic model given insufficient constraints. The screenshot provides insufficient constraints. The model fills the gaps — and the gaps are filled differently each time.&lt;/p&gt;

&lt;p&gt;You can partially work around this with longer, more detailed prompts — "use Tailwind, use 8px grid, use these component names..." — but then you've manually specified the structure that should have been in the design file all along. You're doing the extraction work the tool should do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reproducibility problem
&lt;/h2&gt;

&lt;p&gt;Teams that use screenshots for design-to-code handoff hit the same wall: the output is not reproducible. Two developers, same Figma screenshot, independently prompt Claude — they get different component structures, different className patterns, different nesting decisions. Now you have two codebases that look the same visually but are architecturally inconsistent.&lt;/p&gt;

&lt;p&gt;This makes code review harder. It makes refactoring harder. It makes design system compliance auditing impossible. You can't diff "what did the agent generate from this design" if the answer changes every run.&lt;/p&gt;

&lt;p&gt;Structured context fixes reproducibility because it fixes the inputs. A deterministic input bundle — the same JSON with the same node IDs, component names, token values, and spatial relationships — produces much more consistent output across runs, agents, and developers. Not perfectly deterministic: the model is still probabilistic. But the variance drops dramatically when the structure is specified rather than inferred.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a screenshot gives you vs. what the IR gives you
&lt;/h2&gt;

&lt;p&gt;Take a product card: image, title, subtitle, price, an "Add to cart" button. Here's what each input gives the agent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot input:&lt;/strong&gt; A rectangle with an image at the top, two lines of text, a number, and a button. Colors are inferred. Padding is estimated. Whether this is a component or one-off is unknown. The button variant is inferred from color. The spacing system is unknown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IR input:&lt;/strong&gt; Node kind &lt;code&gt;FRAME&lt;/code&gt;, name &lt;code&gt;ProductCard&lt;/code&gt;, component ID linking to the component definition. Auto-layout with vertical direction, 16px gap, 16px horizontal padding, 12px vertical padding. Child nodes: &lt;code&gt;IMAGE&lt;/code&gt; (fills width, fixed height), &lt;code&gt;TEXT&lt;/code&gt; with &lt;code&gt;stringRef.key: "product.title"&lt;/code&gt; and style &lt;code&gt;typography/heading.sm&lt;/code&gt;, &lt;code&gt;TEXT&lt;/code&gt; with &lt;code&gt;stringRef.key: "product.subtitle"&lt;/code&gt; and style &lt;code&gt;typography/body.md&lt;/code&gt;, &lt;code&gt;TEXT&lt;/code&gt; with fill &lt;code&gt;color/price&lt;/code&gt;, &lt;code&gt;INSTANCE&lt;/code&gt; of &lt;code&gt;Button/Primary/Medium&lt;/code&gt;. Background fill &lt;code&gt;color/surface.card&lt;/code&gt;. Border radius &lt;code&gt;radius/card&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The IR gives the agent a spec. The screenshot gives it a suggestion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frame: this is the documentation problem
&lt;/h2&gt;

&lt;p&gt;We solved this exact problem for source code decades ago. You don't give an agent a screenshot of your codebase and ask it to reason about architecture. You give it the code — the structured, parseable, semantically-meaningful representation. The abstract syntax tree, not a picture of the editor.&lt;/p&gt;

&lt;p&gt;Figma designs are structured data. They have a well-defined tree structure with typed nodes and named values, and the Figma API exposes it completely. The only reason the screenshot workflow persists is that extracting the structure and formatting it as context has friction.&lt;/p&gt;

&lt;p&gt;Reducing that friction is what &lt;a href="https://figmascope.dev" rel="noopener noreferrer"&gt;figmascope&lt;/a&gt; does. You paste the Figma URL, the export runs in your browser, and you get a ZIP with structured context: &lt;code&gt;CONTEXT.md&lt;/code&gt;, &lt;code&gt;tokens.json&lt;/code&gt;, per-screen IR, component inventory, strings manifest. Everything the agent needs, none of it inferred from pixels.&lt;/p&gt;

&lt;p&gt;Keep the screenshots for visual confirmation — the bundle includes 2x PNGs for exactly that. Use the structure for everything else. If you're on Claude Code, the &lt;a href="https://figmascope.dev/blog/claude-code-figma-workflow" rel="noopener noreferrer"&gt;full handoff pipeline walkthrough&lt;/a&gt; shows the pattern end-to-end; there are also variants for &lt;a href="https://figmascope.dev/blog/cursor-figma-workflow" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; and &lt;a href="https://figmascope.dev/blog/aider-figma-workflow" rel="noopener noreferrer"&gt;Aider&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>figma</category>
      <category>ai</category>
      <category>webdev</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
