<?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: Cristian Deluxe</title>
    <description>The latest articles on DEV Community by Cristian Deluxe (@cristiandeluxe).</description>
    <link>https://dev.to/cristiandeluxe</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%2F1382500%2F833aef06-0826-41da-878b-61079d4a2f05.jpeg</url>
      <title>DEV Community: Cristian Deluxe</title>
      <link>https://dev.to/cristiandeluxe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cristiandeluxe"/>
    <language>en</language>
    <item>
      <title>Reserving an ad slot before it moves the page</title>
      <dc:creator>Cristian Deluxe</dc:creator>
      <pubDate>Tue, 29 Sep 2026 14:28:18 +0000</pubDate>
      <link>https://dev.to/cristiandeluxe/reserving-an-ad-slot-before-it-moves-the-page-13d1</link>
      <guid>https://dev.to/cristiandeluxe/reserving-an-ad-slot-before-it-moves-the-page-13d1</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://cristiandeluxe.dev/blog/mobile-cls-ad-slot-reservation/" rel="noopener noreferrer"&gt;https://cristiandeluxe.dev/blog/mobile-cls-ad-slot-reservation/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An ad-size change exposed a layout shift on a financial-media site I worked on. Google Search Console flagged roughly 2,000 mobile URLs. The ad framework inserted a slot above the article after the page had painted, pushing the content down.&lt;/p&gt;

&lt;p&gt;I kept the larger ad sizes and changed how the page made room for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the shift under mobile conditions
&lt;/h2&gt;

&lt;p&gt;I measured with Puppeteer using an iPhone 14 viewport, Fast 3G network throttling and a 4× CPU throttle. The run included scrolling so it could catch shifts from lazy-loaded slots.&lt;/p&gt;

&lt;p&gt;That was an emulated test profile. The roughly 2,000 URLs came from Search Console; I did not run a before-and-after test on every one of them. The reproduced baseline CLS was 0.181.&lt;/p&gt;

&lt;p&gt;I inspected which element moved and what appeared above it. The ad code created its own wrapper in JavaScript after the initial document loaded. Increasing the ad size made the missing reservation more visible.&lt;/p&gt;

&lt;p&gt;A shifted element is a clue to the cause. It can be the content displaced by a new element, rather than the element responsible for the insertion. Google's &lt;a href="https://web.dev/articles/optimize-cls#identify-load-cls-issues" rel="noopener noreferrer"&gt;layout-shift debugging guidance&lt;/a&gt; makes the same distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the space before the ad arrives
&lt;/h2&gt;

&lt;p&gt;I changed the server-rendered template to emit the ad placeholder, with CSS reserving its height for the relevant layout. The ad code then checked for that slot and reused it instead of inserting a second wrapper.&lt;/p&gt;

&lt;p&gt;Both sides had to agree on the slot's identity and geometry. If the client couldn't find the server-rendered element, it would still insert its own. If it found the element but changed its size, content could still move. Reserving space only after JavaScript ran would also leave the initial paint exposed.&lt;/p&gt;

&lt;p&gt;I kept the existing insertion path for slots without a server-rendered placeholder. That made it possible to apply the fix to the affected layouts without changing every ad placement at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check where a placeholder does not belong
&lt;/h2&gt;

&lt;p&gt;Some layouts had no leaderboard ad. Those needed to stay out of the placeholder path. Reserving a blank band on a page that would never fill it would have introduced a visible regression.&lt;/p&gt;

&lt;p&gt;This is the part I would check carefully on another site: test both the layout with the slot and the layout without it. On the first, inspect the document before the ad request finishes, then confirm that initialization reuses the same element. On the second, confirm that the template leaves no reserved gap.&lt;/p&gt;

&lt;p&gt;I would also repeat the throttled run through a scroll, rather than stopping at first paint. Later ad loading can still move content below the fold.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The LEDs weren't listening: a reverse-engineering story</title>
      <dc:creator>Cristian Deluxe</dc:creator>
      <pubDate>Tue, 29 Sep 2026 14:27:42 +0000</pubDate>
      <link>https://dev.to/cristiandeluxe/the-leds-werent-listening-a-reverse-engineering-story-59mk</link>
      <guid>https://dev.to/cristiandeluxe/the-leds-werent-listening-a-reverse-engineering-story-59mk</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://cristiandeluxe.dev/blog/reverse-engineering-mvave-smc-pad-leds/" rel="noopener noreferrer"&gt;https://cristiandeluxe.dev/blog/reverse-engineering-mvave-smc-pad-leds/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wanted the buttons on my M-VAVE SMC-PAD to show what my lighting setup was doing. I use it with QLC+, and the feedback I wanted was fairly ordinary: press AUTO and have the AUTO pad glow green; while a flash is live, have its pad go white. There is an RGB LED under every button. The official app can set the colors. I wanted to control them from my own code.&lt;/p&gt;

&lt;p&gt;I couldn't find public documentation for the LED commands, so I started with the obvious test. I swept every note across every channel and tested control-change messages. Nothing lit up. Those tests gave me no working LED command. It didn't mean the LEDs couldn't be controlled over MIDI; I hadn't found the right kind of message yet.&lt;/p&gt;

&lt;p&gt;The app could do it, so the next useful thing to read was the app.&lt;/p&gt;

&lt;p&gt;MidiSuite on Android is built with Flutter. I used Blutter to turn its Dart AOT snapshot into readable assembly, then followed the color path: &lt;code&gt;sendColorData → writeData → makeWritePacket&lt;/code&gt;. It led to Bluetooth GATT. That explained why tracing the Android app wasn't producing the USB MIDI command I was looking for, but it did give me a way into the packet construction.&lt;/p&gt;

&lt;p&gt;I was pairing with AI on the decompiling and byte-tracing work. There was plenty of mechanical reading for it to do, and it helped turn that reading into hypotheses I could test. Those hypotheses still needed checking. Later, the hardware caught mistakes in both the framing constant and the address we were using.&lt;/p&gt;

&lt;p&gt;There was also a desktop editor: MidiSuite for macOS, also built with Flutter. That was the more useful app for following the USB path. I exported a preset from its editor. Before trying to read more assembly, I could compare the saved file with the colors visible in the editor.&lt;/p&gt;

&lt;p&gt;Each pad's color record had this form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09 &amp;lt;id&amp;gt; 00 7F R G B FF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The color itself was plain 24-bit RGB. I checked the bytes against the on-screen colors, and they matched byte-for-byte. I now had a verified representation of the color in a preset. That was useful, but it wasn't yet a command the pad would accept. A saved record doesn't tell you how the editor sends it, or where the device expects it to go.&lt;/p&gt;

&lt;p&gt;Getting the macOS app into readable form needed some work of its own. Blutter expected ELF, while the snapshot I needed to inspect was in Mach-O. I patched Blutter's experimental Mach-O branch to build a macOS-target Dart runtime. With that in place, the decompile worked, and I could read &lt;code&gt;sysex_codec.dart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Over USB, the color command is manufacturer-specific MIDI SysEx. The logical packet contains ordinary bytes, but SysEx data must stay within 7 bits, so the codec re-encodes the payload as an 8→7-bit bitstream. Knowing the RGB values was only part of the job. I also needed to reproduce that encoding and the framing around it.&lt;/p&gt;

&lt;p&gt;I checked the decoder against the pad's own captured status frames. The test was to round-trip real data through the codec and reproduce what the hardware had sent. That gave me something firmer than an interpretation of decompiled code: either the bytes matched or they didn't.&lt;/p&gt;

&lt;p&gt;They exposed the first mistake. The AI's initial read had given the framing constant as &lt;code&gt;0xB2&lt;/code&gt;. The correct constant was &lt;code&gt;0x59&lt;/code&gt;. Passing &lt;code&gt;toMidi([00,0x59,…])&lt;/code&gt; reproduced the device's real &lt;code&gt;F0 00 32 …&lt;/code&gt; header. I corrected the constant on that evidence. Without the captured frames, I would have been debugging writes with a codec that was already wrong.&lt;/p&gt;

&lt;p&gt;The USB color-write packets I captured began with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;F0 00 32 09 59 …
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the 8→7-bit encoding, the logical color-write packet is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 59 22 &amp;lt;len24&amp;gt; 05 &amp;lt;flash-addr-le32&amp;gt; 03 00 00 R G B &amp;lt;checksum&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The checksum is &lt;code&gt;checksum = ~sum(data) &amp;amp; 0xFF&lt;/code&gt;, where &lt;code&gt;data&lt;/code&gt; is the payload starting at &lt;code&gt;05&lt;/code&gt;, excluding the logical header and length field. These are different views of the command: the logical bytes and their SysEx representation. Keeping them separate mattered when comparing what I had decoded with what was actually being sent over USB.&lt;/p&gt;

&lt;p&gt;The address needed its own check. The AI pair had also confused an offset in the file with an address on the device. I checked against the config dump read live from the pad and used the actual device flash addresses. In the configuration I tested, bank A pad 1 was at &lt;code&gt;0x418&lt;/code&gt;, with a 26-byte stride between pads. The preset had established how the color was represented; the live dump established where it belonged on the hardware.&lt;/p&gt;

&lt;p&gt;At that point I had checked the transport, codec, framing, checksum and addresses. My writes still did nothing.&lt;/p&gt;

&lt;p&gt;This was the part that held me up. The desktop app could send identical bytes through the identical CoreMIDI path and light the LED immediately. My code sent them and the pad ignored them. I had corrected real mistakes, but neither correction explained that remaining difference. Looking at the color-write packet on its own wasn't getting me any further.&lt;/p&gt;

&lt;p&gt;The comparison needed to include what had happened before the write. I used a CoreMIDI output spy to capture the app's traffic from the very first packet, including the connection sequence. Until then, I had the command I wanted to reproduce without the complete conversation that made it work.&lt;/p&gt;

&lt;p&gt;The app began with discovery, then read the entire configuration back through a run of requests with incrementing addresses. The color write happened after that exchange. I replayed the whole connect handshake and injected my write into the session. The pad changed color the instant the write landed.&lt;/p&gt;

&lt;p&gt;In my tests, writes were gated by the session. A one-shot send was ignored. After discovery and the full config read, the pad accepted the color command. I had been comparing identical payloads on the same transport while leaving out the state established by the earlier packets.&lt;/p&gt;

&lt;p&gt;The fix I verified was replaying the complete session. I wouldn't shorten that to "send a connect packet first," because the working sequence included the full configuration read. That distinction matters to anyone trying to repeat it: the capture contains the exchange that worked, not just a color command extracted from the middle of it.&lt;/p&gt;

&lt;p&gt;Once the session was replayed, I could put a rainbow across all 16 pads. That was the visible result I had been missing through all the byte comparisons. I also got it working over Bluetooth GATT, so the LED control worked wirelessly too. The working Bluetooth replay sends logical packets through service &lt;code&gt;AE40&lt;/code&gt;, with &lt;code&gt;AE41&lt;/code&gt; for writes and &lt;code&gt;AE42&lt;/code&gt; for notifications, without the USB 7-bit transform.&lt;/p&gt;

&lt;p&gt;Looking back, I got useful evidence whenever I made a different part of the system readable. The Android decompile showed the GATT path. The exported preset let me check RGB values against the GUI. The macOS decompile exposed the USB codec, and the output spy finally showed the session that a single captured write couldn't explain. None of those views was enough by itself.&lt;/p&gt;

&lt;p&gt;The same applies to the AI work. It helped with the mechanical reads and proposed explanations, but I had to test those explanations against the device. The wrong constant failed the status-frame round-trip. The file-offset assumption failed the comparison with the live config addresses. Both were concrete errors I could correct because I had an independent reference.&lt;/p&gt;

&lt;p&gt;If I hit the identical-bytes problem again, I'll capture from the beginning of the connection sooner. Here, the deciding test was replaying what the editor did before it changed a color. I kept the working code and captured session in &lt;a href="https://github.com/spectalive/smc-pad" rel="noopener noreferrer"&gt;spectalive/smc-pad&lt;/a&gt;, including the sequence I used to get the pad listening.&lt;/p&gt;

</description>
      <category>reverseengineering</category>
      <category>midi</category>
      <category>programming</category>
    </item>
    <item>
      <title>Running my companies’ software with an agent fleet</title>
      <dc:creator>Cristian Deluxe</dc:creator>
      <pubDate>Tue, 29 Sep 2026 14:26:55 +0000</pubDate>
      <link>https://dev.to/cristiandeluxe/running-my-companies-software-with-an-agent-fleet-14i2</link>
      <guid>https://dev.to/cristiandeluxe/running-my-companies-software-with-an-agent-fleet-14i2</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://cristiandeluxe.dev/blog/running-a-company-on-an-agent-fleet/" rel="noopener noreferrer"&gt;https://cristiandeluxe.dev/blog/running-a-company-on-an-agent-fleet/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I use concurrent coding-agent sessions to work on my companies' software. I built the shared infrastructure those sessions use, including a private knowledge wiki and a pipeline for adding sources to it. I also operate the memory service behind that work.&lt;/p&gt;

&lt;p&gt;The useful details are in the failures. In August 2026, I was dealing with a growing local memory store, competing writers and model fallbacks that could undermine review. These are the checks and measurements from that period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each session a place to start
&lt;/h2&gt;

&lt;p&gt;My context layer is a cross-linked Markdown wiki, versioned in Git. It holds project setup and past decisions, with links back to the sources. A new session can look up why something was done before proposing to change it.&lt;/p&gt;

&lt;p&gt;I built an ingest pipeline for captured web content. A bulk model classifies the captures; a stronger model synthesizes the selected material into wiki pages. Validation rejects pages with no incoming links and writes outside the allowed paths. The workflow also requires a human diff review before publication.&lt;/p&gt;

&lt;p&gt;On August 7, I captured a batch of 103 open research tabs. The log records 94 threads reaching classification: 50 selected for ingestion and 44 rejected. I kept the raw captures alongside the synthesis so I could check a page against what I had actually read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the author and verifier separate
&lt;/h2&gt;

&lt;p&gt;The synthesis workflow uses a second model to grade pages against their sources. I enforce author/verifier separation in code: the grader must not resolve to the model that wrote the page. That includes fallback selection. Otherwise a routing failure can quietly turn an independent check into the author reviewing its own output.&lt;/p&gt;

&lt;p&gt;I route large classification passes to a model tier with enough quota to finish them. Synthesis and grading use the scarcer tier. The selectors reject unrecognized model names, so a typo stops the job instead of silently changing its routing.&lt;/p&gt;

&lt;p&gt;That separation gives me a check to inspect. It does not make the generated pages correct by itself; I still need the source links and the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the memory service under load
&lt;/h2&gt;

&lt;p&gt;At the time, the local semantic store contained roughly 700,000 entries mined from repositories and session transcripts. I ran a fork of the memory engine because several operational fixes needed to live in the code.&lt;/p&gt;

&lt;p&gt;One project-mine measurement on August 4 took 5.3 seconds against an empty store and 190 seconds against a store with 653,000 entries, for the same 448-item project. That was roughly 36 times slower. The mining path checked each file with a collection-wide query. I replaced those repeated lookups with a prefetch scan for larger projects, keeping a final check under the write lock.&lt;/p&gt;

&lt;p&gt;A separate curation run showed why adding writers was the wrong response to a queue. Serial processing produced about 7.5 entries per minute. Parallel reads with batched writes reached about 33 entries per minute, a 4.4-fold increase. The writes still went through one writer. The gain came from batching work through that writer, not from letting multiple processes update the vector index.&lt;/p&gt;

&lt;p&gt;The store had already suffered index divergence twice. I kept the single-writer lock and treated it as a constraint when changing throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make recovery stop where it should
&lt;/h2&gt;

&lt;p&gt;I added a watchdog that checks the daemon and index every 15 minutes. It can restart a wedged daemon, but it abstains when a command-line mining job holds the writer lock. The recovery path depends on what failed; a running process alone is not enough to call the service healthy.&lt;/p&gt;

&lt;p&gt;Concurrent sessions also share Git repositories. I require each session to stage only its own paths and push completed commits promptly. A formatting hook catches another source of avoidable conflicts. Before restoring a file, the session has to check whether another session changed it.&lt;/p&gt;

&lt;p&gt;Those rules let me keep several sessions working without treating another session's unfinished work as something to clean up. When a check fails, I want the job to stop with enough evidence to tell me which operation failed and what it had already written.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
