<?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: Arthur031221</title>
    <description>The latest articles on DEV Community by Arthur031221 (@arthur031221).</description>
    <link>https://dev.to/arthur031221</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%2F4152010%2Fdc59e799-61f4-4c44-aa6a-204b14048265.jpeg</url>
      <title>DEV Community: Arthur031221</title>
      <link>https://dev.to/arthur031221</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arthur031221"/>
    <language>en</language>
    <item>
      <title>I built a free local editor that cuts talking videos from the transcript</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Mon, 09 Nov 2026 01:07:03 +0000</pubDate>
      <link>https://dev.to/arthur031221/i-built-a-free-local-editor-that-cuts-talking-videos-from-the-transcript-e6c</link>
      <guid>https://dev.to/arthur031221/i-built-a-free-local-editor-that-cuts-talking-videos-from-the-transcript-e6c</guid>
      <description>&lt;p&gt;Cutting ums and long pauses out of a talking video is slow in a timeline editor. You find each one by ear, split, ripple delete, and repeat. Paid tools like Descript turned this into text editing, and I wanted the same thing on my own machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fccr1yg40rq470svvnuqo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fccr1yg40rq470svvnuqo.gif" alt="Spokecut demo" width="80" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Spokecut transcribes a video with faster-whisper, shows the words, and lets you delete them. A deleted word is gone from the video. The editor has buttons to shorten pauses and remove repeats, a search that finds a phrase and cuts every match, and playback that skips what you cut. Captions come in five styles and can be burned in or exported as SRT, VTT and ASS. The edit can also be exported as EDL, FCPXML or OTIO. The source video is never modified. The edit is saved next to it as a small JSON file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"spokecut[cuda] @ git+https://github.com/Arthur031221/Spokecut"&lt;/span&gt;
spokecut talk.mp4
spokecut clean talk.mp4 &lt;span class="nt"&gt;--pauses&lt;/span&gt; 0.7 &lt;span class="nt"&gt;--captions&lt;/span&gt; highlight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How a cut stays clean
&lt;/h2&gt;

&lt;p&gt;Each cut is moved to the quietest point between two words, so it lands in silence. The render is one ffmpeg filter graph with frame exact trims, and every join has a 15 ms fade. On a 300 second test video, 3 of 37 joins stepped above 10 times the typical sample change without the fade and none did with it. That counts steps in the signal. I did not run a listening test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;On an RTX 5090 a 5 minute video goes from file to a finished MP4 with burned in captions in 15.0 s, about 20 times realtime (4 runs, 14.7 to 15.1 s, large-v3-turbo and NVENC). On a Ryzen 5 7500F with 3 threads, the small model and x264, one run took 61.1 s. The method for every number is in docs/benchmarks.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is rough
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Stock Whisper drops most ums, so the filler button finds little. A verbatim mode uses CrisperWhisper, which keeps them but is licensed for non-commercial use only.&lt;/li&gt;
&lt;li&gt;It handles one speaker per file.&lt;/li&gt;
&lt;li&gt;I read the EDL, FCPXML and OTIO files back through the OpenTimelineIO adapters in tests, but I have not imported them into Resolve or Premiere.&lt;/li&gt;
&lt;li&gt;I have only run it on Linux. The CI covers Linux.&lt;/li&gt;
&lt;li&gt;The hosted demo runs the editor on a sample clip. It cannot transcribe your own video or render an MP4.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The code is MIT licensed at &lt;a href="https://github.com/Arthur031221/Spokecut" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Spokecut&lt;/a&gt; and the browser demo is at &lt;a href="https://arthur031221.github.io/Spokecut/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Spokecut/&lt;/a&gt;. If you cut talking head video, tell me which export or edit action you miss first. The tool is built with AI assistance, and I checked each number in the README by running the command it names.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>video</category>
      <category>whisper</category>
    </item>
    <item>
      <title>A free party game box you host yourself: one engine, a WebSocket server and a WebAssembly demo</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Sun, 08 Nov 2026 01:07:03 +0000</pubDate>
      <link>https://dev.to/arthur031221/a-free-party-game-box-you-host-yourself-one-engine-a-websocket-server-and-a-webassembly-demo-3dlh</link>
      <guid>https://dev.to/arthur031221/a-free-party-game-box-you-host-yourself-one-engine-a-websocket-server-and-a-webassembly-demo-3dlh</guid>
      <description>&lt;p&gt;I wanted a party game night where nobody has to buy a pack, make an account or install an app. So I built Couchcade, a free game box you host yourself. It's one Go binary. You open it on a TV or laptop, people scan a QR code, type a four letter code and play from their own phones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4g7u2samhzubl94spnk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4g7u2samhzubl94spnk.gif" alt="A Squiggle round on the TV with phones voting" width="719" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can try it without installing anything. The &lt;a href="https://arthur031221.github.io/Couchcade/" rel="noopener noreferrer"&gt;hosted demo&lt;/a&gt; runs the same engine compiled to WebAssembly in your browser, with bots in the seats.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is in it
&lt;/h2&gt;

&lt;p&gt;Six original games: Squiggle (draw the prompt, vote for the best), Fibber (write a fake answer, find the real one), Brain Teams (team trivia), Zap (a reaction test), Echo (match answers) and Mole (find the player with no secret word). Eight people get a seat and up to 500 more join as audience. Bots fill empty seats. The interface and prompts come in nine languages, and the prompts are written per language, not translated word for word.&lt;/p&gt;

&lt;h2&gt;
  
  
  One engine, two homes
&lt;/h2&gt;

&lt;p&gt;The engine has no HTTP in it. It talks to a connection interface with a non blocking send, and a room lock serializes every change. State goes out as one message per connection, flushed every 50 ms, and big payloads like drawings only travel again when they change. The server adapts WebSockets to that interface. The WebAssembly build adapts a JavaScript callback to it. Both run the same code, so the hosted demo is the real game.&lt;/p&gt;

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

&lt;p&gt;With 100 simulated phones over loopback, every phone got the first question within 60 ms of the host pressing Start, in 12 of 12 runs. Most of that is the 50 ms batching tick. It only measures the server. Wi-Fi isn't in it and nothing is drawn on screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a review found
&lt;/h2&gt;

&lt;p&gt;Before publishing I asked for a hostile review of the code. It found real problems. A single TV connection could open rooms in a loop until the code generator spun forever. A phone that sent join twice left a ghost player behind. If every phone dropped at once, a game skipped through its rounds. The audience could outvote the seated players in Fibber. All of these are fixed, each with a test. Two issues remain by design and are in the README: kicking bans a browser token, not a person, and room codes are only four letters, so keep it off networks you don't trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/Arthur031221/Couchcade/cmd/couchcade@latest
couchcade serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompts are plain JSON, so you can add your own pack and check it with &lt;code&gt;couchcade packs validate&lt;/code&gt;. The code is MIT licensed at &lt;a href="https://github.com/Arthur031221/Couchcade" rel="noopener noreferrer"&gt;github.com/Arthur031221/Couchcade&lt;/a&gt;. Prompts and translations are the easiest way to contribute.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>A practice studio that splits a song into six stems, and what I measured</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Thu, 29 Oct 2026 01:07:04 +0000</pubDate>
      <link>https://dev.to/arthur031221/a-practice-studio-that-splits-a-song-into-six-stems-and-what-i-measured-20i6</link>
      <guid>https://dev.to/arthur031221/a-practice-studio-that-splits-a-song-into-six-stems-and-what-i-measured-20i6</guid>
      <description>&lt;p&gt;I learn songs by ear, and the loop is always the same: split the track somewhere, look up the chords somewhere else, then fight a tempo changer that shifts the pitch. I wrote Stemwise to keep that loop on my own machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fual65w09efrvitgrz1ys.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fual65w09efrvitgrz1ys.gif" alt="Stemwise muting the drums, looping a bar, slowing to 70 percent and transposing" width="200" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Drop an audio file on a local page. Stemwise separates it into vocals, drums, bass, guitar, piano and other with Demucs &lt;code&gt;htdemucs_6s&lt;/code&gt;, tracks beats and downbeats with beat_this, and estimates the chords and key. The page then opens a player where you can mute or solo any stem, loop a bar with &lt;code&gt;L&lt;/code&gt;, slow the song down with the pitch held, transpose, and play along to a click that follows the song's own beat grid. The export menu gives you a backing track, the stems, a chord sheet and the beat grid as MIDI.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the pitch stays put
&lt;/h2&gt;

&lt;p&gt;The browser engine is an AudioWorklet that does WSOLA time stretching, with a cubic resampler for transposing. A unit test counts the zero crossings of a 440 Hz sine at 50, 70 and 150 percent speed and checks that the pitch doesn't move.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;stemwise bench&lt;/code&gt; runs the separation on a 2 minute 3 second song. On an RTX 5090 it takes 1.00 s, which is 122.5x realtime, best of 3 after one warmup run in fp32. On a Ryzen 5 7500F with 3 threads it takes 45.2 s, or 2.7x realtime.&lt;/p&gt;

&lt;p&gt;The chords come from a CQT chroma model with templates, a bass bias and Viterbi smoothing. I tuned the constants on one clip in five of GuitarSet and scored the other 288. Root accuracy is 0.739 on strummed guitar and 0.265 on solos. Beats are reliable on the strummed parts. A solo of single notes carries little chord information, so the track is wrong there most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;The key is one guess for the whole song. I've only run it on Linux, the package isn't on PyPI yet, and the live demo replays one separated song instead of processing your files. Only one song is processed at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/Arthur031221/Stemwise
stemwise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is MIT licensed at &lt;a href="https://github.com/Arthur031221/Stemwise" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Stemwise&lt;/a&gt; and the demo is at &lt;a href="https://arthur031221.github.io/Stemwise/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Stemwise/&lt;/a&gt;. I built it with help from AI coding assistants. Which song should I try that would break the chord track?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Restoring old family photos locally, and saying what the model invented</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Tue, 27 Oct 2026 05:07:02 +0000</pubDate>
      <link>https://dev.to/arthur031221/restoring-old-family-photos-locally-and-saying-what-the-model-invented-5c5c</link>
      <guid>https://dev.to/arthur031221/restoring-old-family-photos-locally-and-saying-what-the-model-invented-5c5c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fizmr62xeakznpy39v17l.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fizmr62xeakznpy39v17l.gif" alt="Unfaded cutting four prints from a scan, restoring them and showing what each step changed" width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Old family photos are the thing most people want restored and least want to upload. The research models for it exist, but each one is a separate repository with its own command line, and two of the best known are archived. I wrote Unfaded to put them in one pipeline that runs on a local GPU and tells you what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;p&gt;A scan goes through six steps, and each one can be switched off.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Split cuts every print out of a flatbed scan and straightens it. It uses classical OpenCV, not a model.&lt;/li&gt;
&lt;li&gt;Fade restores levels and removes the colour cast. It skips itself when the print already spans 80 percent of the tonal range.&lt;/li&gt;
&lt;li&gt;Repair finds scratches, creases and dust with an old photo scratch detector and fills them with LaMa. It skips itself if more than 15 percent of the print would be filled.&lt;/li&gt;
&lt;li&gt;Faces runs GFPGAN with a strength slider per face. The default is 35, because at 100 a face can look like someone else.&lt;/li&gt;
&lt;li&gt;Colour uses DDColor on black and white prints and never changes lightness.&lt;/li&gt;
&lt;li&gt;Enlarge uses Real-ESRGAN at 2x or 4x, in tiles for large prints.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Saying what the model invented
&lt;/h2&gt;

&lt;p&gt;Models make things up, so the tool says so. Every step that drew pixels is labelled as invented by the model, and an Invented view paints exactly where. Each result gets an XMP sidecar with the sha256 of the source file, the model and settings of every step, and the steps where a model generated pixels. The original is never touched.&lt;/p&gt;

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

&lt;p&gt;I treated the four photos in &lt;code&gt;examples/photos&lt;/code&gt; as clean references, drew scratches, creases and dust on them with a known mask at fixed seeds, and ran only the repair step. Mean PSNR rose from 26.8 to 29.0 dB. PSNR improved on three of the four photos and SSIM on two. On one portrait the detector marked parts of the texture as damage and the fill made it slightly worse. The splitter found 12 of 12 prints on synthetic scans with a mean IoU of 0.95.&lt;/p&gt;

&lt;p&gt;Those numbers come from damage I drew myself, which is easier than real wear, so I read them as a regression check and not as a promise about your photos. &lt;code&gt;unfaded bench&lt;/code&gt; reproduces them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;The scratch detector misses thin dark lines. DDColor guesses colour and leans warm. Tone recovery turns sepia prints neutral grey. I have only run it on Linux with an RTX 5090. The live demo is a replay of precomputed output.&lt;/p&gt;

&lt;p&gt;Code and live demo: &lt;a href="https://github.com/Arthur031221/Unfaded" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Unfaded&lt;/a&gt; and &lt;a href="https://arthur031221.github.io/Unfaded/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Unfaded/&lt;/a&gt;. It is MIT licensed, and the model weights keep their own licenses.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>How a peak finder decides which mountains you can see, and what I measured</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:07:02 +0000</pubDate>
      <link>https://dev.to/arthur031221/how-a-peak-finder-decides-which-mountains-you-can-see-and-what-i-measured-3bl2</link>
      <guid>https://dev.to/arthur031221/how-a-peak-finder-decides-which-mountains-you-can-see-and-what-i-measured-3bl2</guid>
      <description>&lt;p&gt;I wanted a peak finder whose method I can read, change and host myself. So I built WhichPeak, a static web app that draws a labeled 360 degree panorama from open elevation tiles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cebpq5ras63tufluyjb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cebpq5ras63tufluyjb.gif" alt="Turning the Rigi panorama toward the Bernese Alps, opening the card for Jungfrau, then moving the sun to golden hour" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You open a link or drop a pin. WhichPeak reads AWS Terrain Tiles, builds the terrain in six rings of detail from 5 km out to 1000 km, and draws the view. It adds earth curvature and refraction, shades the terrain with the real sun for the hour you pick, and labels each summit that is actually above the horizon. Click a label for elevation, distance, bearing, the angle above the horizon and how far the peak clears the terrain in front of it.&lt;/p&gt;

&lt;p&gt;There is also a line of sight tool that plots the profile between two points and names the ridge that blocks it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the horizon works
&lt;/h2&gt;

&lt;p&gt;For each pixel column the renderer sweeps outward and keeps the highest slope it has seen. Curvature drops the ground by d squared over twice the effective radius, and refraction enlarges that radius by 1 over (1 minus k) with k set to 0.13. A peak gets a label only if its own slope beats the sweep. The known flaw is a smear in the foreground on very steep ground. It is cosmetic and does not touch the labels.&lt;/p&gt;

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

&lt;p&gt;The terrain for a 150 km view from the Gornergrat is drawn in 4.6 s, the median of six cold runs in headless Chrome over the real network. At 400 km it is 6.6 s. Peak names come from Overpass and load separately, between 10 and 32 s in my runs. A bundled list of 35,621 summits above 1800 m fills in when no mirror answers.&lt;/p&gt;

&lt;p&gt;I also tested the line of sight against sightlines that people documented. Pic de Finestrelles to Pic Gaspard, 443 km, comes out blocked by 48 m at the average refraction and visible from k = 0.145. That matches a photograph taken in 2016 under strong refraction. Marys Peak to Mount Rainier, 295.8 km, is visible with 78 m of clearance.&lt;/p&gt;

&lt;p&gt;Photo mode is experimental. I ran eight casual photos with the place set and no heading. One matched and seven were flagged as weak matches. Nothing was reported as matched when it was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Arthur031221/WhichPeak.git
&lt;span class="nb"&gt;cd &lt;/span&gt;WhichPeak
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The live demo is at &lt;a href="https://arthur031221.github.io/WhichPeak/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/WhichPeak/&lt;/a&gt; and the code is at &lt;a href="https://github.com/Arthur031221/WhichPeak" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/WhichPeak&lt;/a&gt;. The interface comes in nine languages and the project has 93 unit tests and 10 browser tests. I built it with help from AI coding assistants.&lt;/p&gt;

&lt;p&gt;Which long sightline should I test next?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Token Beggar 0.3.0: on 18 real bug fixes it cost 1.42x less, with a tie on tests</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:30:32 +0000</pubDate>
      <link>https://dev.to/arthur031221/token-beggar-030-on-18-real-bug-fixes-it-cost-142x-less-with-a-tie-on-tests-5305</link>
      <guid>https://dev.to/arthur031221/token-beggar-030-on-18-real-bug-fixes-it-cost-142x-less-with-a-tie-on-tests-5305</guid>
      <description>&lt;p&gt;I made a Claude Code plugin called Token Beggar, and today I released version 0.3.0 of it.&lt;/p&gt;

&lt;p&gt;I kept running the biggest model at high effort for every step, even when the step was just reading a repo I had never seen. The bill made me curious, so I built something that checks whether a cheaper model can do the job first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FToken-Beggar%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FToken-Beggar%2Fmain%2Fassets%2Fdemo.gif" alt="Token Beggar demo: the routing card, the doctor check and the benchmark table" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It uses the Claude Code you already have. No extra API, no extra account. It is still in development, and I would love feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 0.3.0 does
&lt;/h2&gt;

&lt;p&gt;Before the big model starts, the plugin sizes the task.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small tasks stay in the chat.&lt;/li&gt;
&lt;li&gt;A large task, like fixing a bug in a repository you have not read yet, goes to one cheaper worker. The worker reproduces, explores, fixes and runs the tests.&lt;/li&gt;
&lt;li&gt;Then the chat reads the whole diff, runs the tests again, and sends the worker back if something is missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Design, root causes and the final answer stay with the model you picked. Your chat model and effort are a ceiling, so nothing goes above them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result, in plain terms
&lt;/h2&gt;

&lt;p&gt;There are two setups, and only two. Without Token Beggar is the chat model working alone. With Token Beggar is the card shipped in 0.3.0. The chat model is Opus 5.5 at high effort, and cost is list price for the chat and every worker, cache included.&lt;/p&gt;

&lt;p&gt;The hard tasks are 18 merged pull requests from 12 open source Python projects (Django, Sphinx, pylint, sympy, click and others). The chat starts from the commit before the fix with only the issue text, and it is scored by the tests the maintainers added, which it never sees. 3 runs per task, 54 runs per setup.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Cost per task&lt;/th&gt;
&lt;th&gt;Multiple&lt;/th&gt;
&lt;th&gt;Issues resolved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without Token Beggar&lt;/td&gt;
&lt;td&gt;$0.69&lt;/td&gt;
&lt;td&gt;1.00x&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With Token Beggar&lt;/td&gt;
&lt;td&gt;$0.49&lt;/td&gt;
&lt;td&gt;1.42x less (1.33 to 1.51)&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66bwgsrix7i4m6hg6cfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66bwgsrix7i4m6hg6cfj.png" alt="Bar chart: how many times less each hard task costs with Token Beggar than without it, 1.42x over all tasks" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first chart shows how many times less each of the 18 tasks cost with Token Beggar. 17 bars are past the 1.0x line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66l4896pz4qunw4ingf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F66l4896pz4qunw4ingf6.png" alt="Bar chart: share of issues resolved, 70% without Token Beggar and 69% with it" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second chart shows the share of issues resolved, 70% without and 69% with.&lt;/p&gt;

&lt;p&gt;Cost per task went down about 29 percent. The share of issues resolved is level, and 69% against 70% is a tie within the spread. Time is level too (168 seconds against 187), so it is not faster. The one task that cost more cost about 10% more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is not the 1.24x from my first article
&lt;/h2&gt;

&lt;p&gt;My earlier article reported 1.24x cheaper overall. That came from a mix of 21 mostly small tasks, and the routing was different then. Small tasks save very little. Under about 1.15x I cannot tell a saving from run to run noise, so 0.3.0 keeps small tasks in the chat and only hands large ones to a worker.&lt;/p&gt;

&lt;p&gt;That change is why the hard task number is bigger. It is also why I do not claim 1.42x for everything. Small tasks were measured only with the earlier card, and they saved little there (1.11x).&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;You need Claude Code and Python 3.10 or newer. It uses the standard library only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add Arthur031221/Token-Beggar
/plugin install token-beggar@token-beggar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the agent or run &lt;code&gt;/reload-plugins&lt;/code&gt;. To check that the hooks run, use &lt;code&gt;token-beggar doctor&lt;/code&gt;. To turn it off, set &lt;code&gt;TOKEN_BEGGAR=off&lt;/code&gt; for a shell, or uninstall the plugin.&lt;/p&gt;

&lt;p&gt;It runs on your machine, writes only to its own folder, and sends nothing about you anywhere. The only network call is one weekly download of the vendor's public models page, to keep prices current.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits
&lt;/h2&gt;

&lt;p&gt;I would rather you read these before you try it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The hard benchmark covers one chat model and effort (Opus 5.5, high) and Python projects. Sonnet, Haiku and Fable on large tasks are not measured.&lt;/li&gt;
&lt;li&gt;69% against 70% resolved is a tie within the spread. A blind review by a small model rated the 0.3.0 diffs lower than working alone (0.94 against 1.33 on a 0 to 2 scale). It agrees only weakly with the tests, so I cannot say the code is as good.&lt;/li&gt;
&lt;li&gt;0.3.0 was not re-run on the small tasks, so expect close to no change on small one-file edits.&lt;/li&gt;
&lt;li&gt;The footer estimate comes from a measured ratio, not from your own task.&lt;/li&gt;
&lt;li&gt;On Haiku, the cheapest model, it does nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The raw runs and the task list are in the repo, so you can check every number.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Arthur031221/Token-Beggar" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Token-Beggar&lt;/a&gt;&lt;br&gt;
Release: &lt;a href="https://github.com/Arthur031221/Token-Beggar/releases/tag/v0.3.0" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Token-Beggar/releases/tag/v0.3.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assisted by Claude/Codex.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Which phrases make a paragraph read like AI? I built a heatmap and measured it on HC3</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:07:02 +0000</pubDate>
      <link>https://dev.to/arthur031221/which-phrases-make-a-paragraph-read-like-ai-i-built-a-heatmap-and-measured-it-on-hc3-21g4</link>
      <guid>https://dev.to/arthur031221/which-phrases-make-a-paragraph-read-like-ai-i-built-a-heatmap-and-measured-it-on-hc3-21g4</guid>
      <description>&lt;p&gt;I wanted to see which exact phrases make a paragraph read like a language model wrote it, rather than get one opaque score. So I built AiSniff.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FAiSniff%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FAiSniff%2Fmain%2Fassets%2Fdemo.gif" alt="AiSniff marking AI-sounding phrases in a paragraph" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You paste English or Traditional Chinese text. AiSniff underlines the phrases that match a documented AI writing pattern. Hover one and you see the rule, a public source and a plain rewrite hint. A side panel groups the hits by type, a meter shows hits per 1,000 words or characters, and a compare view puts a before and after text side by side. You can export the marked text as a PNG.&lt;/p&gt;

&lt;p&gt;It runs fully in the browser. There is no server and no model call, so nothing you paste leaves the page. The same JSON rule packs also ship as a command line tool and an agent skill, so you can run &lt;code&gt;npx github:Arthur031221/AiSniff lint draft.md&lt;/code&gt; in CI and fail a build when the density is too high.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules
&lt;/h2&gt;

&lt;p&gt;There are 61 rules in versioned JSON packs for English and Traditional Chinese. Every rule has a pattern, an explanation, a source citation and examples that should and should not match. The examples run as unit tests, so a rule cannot be added without showing a legitimate use of the same words. Sources are the Wikipedia guide on signs of AI writing, three public pattern lists, the Chinese Wikipedia page on Europeanized Chinese and a peer reviewed study of excess vocabulary.&lt;/p&gt;

&lt;h2&gt;
  
  
  How well does it work
&lt;/h2&gt;

&lt;p&gt;I measured it on a held out half of HC3, a public set of questions with human and ChatGPT answers. At least one English rule fires on 28% of ChatGPT answers and 2% of human answers. For Chinese it is 24% and 6%.&lt;/p&gt;

&lt;p&gt;That is a weak detector, and I would not use the density as proof of anything. The AUROC is 0.63 for English and 0.59 for Chinese, and 72% of the English ChatGPT answers have no hit at all. The data comes from an old model, in a question and answer register, and the Chinese half is Simplified only, so the rules run through a character by character conversion. Some rules, such as the markdown bold rule, did not hold up on HC3, and the evaluation page lists them.&lt;/p&gt;

&lt;p&gt;So AiSniff reports patterns, not authorship. A clean result does not prove a person wrote the text, and a hit does not prove a model did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/Arthur031221/AiSniff" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/AiSniff&lt;/a&gt; and the live demo is at &lt;a href="https://arthur031221.github.io/AiSniff/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/AiSniff/&lt;/a&gt;. It is MIT licensed. I built it with help from Claude Code and Codex. If a rule misfires on your own writing, please open an issue with the sentence.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Does routing coding agent subtasks to cheaper models pay off? A benchmark of 63 runs per arm</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:07:02 +0000</pubDate>
      <link>https://dev.to/arthur031221/does-routing-coding-agent-subtasks-to-cheaper-models-pay-off-a-benchmark-of-63-runs-per-arm-4cdd</link>
      <guid>https://dev.to/arthur031221/does-routing-coding-agent-subtasks-to-cheaper-models-pay-off-a-benchmark-of-63-runs-per-arm-4cdd</guid>
      <description>&lt;p&gt;Update: version 0.3.0 is out, with a harder benchmark and different numbers. The v0.3.0 results are here: &lt;a href="https://dev.to/arthur031221/token-beggar-030-on-18-real-bug-fixes-it-cost-142x-less-with-a-tie-on-tests-5305"&gt;https://dev.to/arthur031221/token-beggar-030-on-18-real-bug-fixes-it-cost-142x-less-with-a-tie-on-tests-5305&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted a straight answer to one question: if a coding agent hands its cheap subtasks to a smaller model, how much does a session really cost less, and does the result get worse?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FToken-Beggar%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FArthur031221%2FToken-Beggar%2Fmain%2Fassets%2Fdemo.gif" alt="Token Beggar demo: the routing card, the doctor check and the benchmark table" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Token Beggar is a plugin that adds a routing card to the session. The chat model grades each task into one of 7 classes and delegates bulk work (large reads, repeated edits, test loops) to a worker with its own context, at a cheaper model and lower effort. Design and root-cause work stays with the chat model. The chat model and effort are a ceiling, so nothing is routed above what you chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;21 tasks on a small Python shop project, 3 per class, 3 runs each.&lt;/li&gt;
&lt;li&gt;Two arms per task, same prompt, same allowed tools, same chat model (Opus 5.5 at high effort).&lt;/li&gt;
&lt;li&gt;Code tasks: a hidden check plus the project's own tests. Answer tasks: a checklist scored by a blind judge that never sees the arm.&lt;/li&gt;
&lt;li&gt;Cost from the agent's JSON output, at list price.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Without&lt;/th&gt;
&lt;th&gt;With&lt;/th&gt;
&lt;th&gt;Cheaper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-file feature&lt;/td&gt;
&lt;td&gt;$2.47&lt;/td&gt;
&lt;td&gt;$1.58&lt;/td&gt;
&lt;td&gt;1.56x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mechanical edits&lt;/td&gt;
&lt;td&gt;$1.11&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;td&gt;1.4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading&lt;/td&gt;
&lt;td&gt;$0.91&lt;/td&gt;
&lt;td&gt;$0.75&lt;/td&gt;
&lt;td&gt;1.21x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design&lt;/td&gt;
&lt;td&gt;$0.93&lt;/td&gt;
&lt;td&gt;$0.82&lt;/td&gt;
&lt;td&gt;1.14x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routine code&lt;/td&gt;
&lt;td&gt;$1.47&lt;/td&gt;
&lt;td&gt;$1.32&lt;/td&gt;
&lt;td&gt;1.12x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug root cause&lt;/td&gt;
&lt;td&gt;$1.45&lt;/td&gt;
&lt;td&gt;$1.29&lt;/td&gt;
&lt;td&gt;1.12x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;$1.21&lt;/td&gt;
&lt;td&gt;$1.13&lt;/td&gt;
&lt;td&gt;1.07x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All 63 runs per arm&lt;/td&gt;
&lt;td&gt;$9.56&lt;/td&gt;
&lt;td&gt;$7.69&lt;/td&gt;
&lt;td&gt;1.24x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quality was 0.96 in both arms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would not claim
&lt;/h2&gt;

&lt;p&gt;Design, bug root cause and review never go to a worker, yet they came out 1.07x to 1.14x cheaper. That is run to run spread plus, I suspect, a shorter path the card gives the chat model. So I only quote multipliers of 1.15x or more in the plugin's footer. The routes were tuned on the same 21 tasks, so the table describes this setup and is not a forecast for yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  A scoring bug worth knowing about
&lt;/h2&gt;

&lt;p&gt;One debugging task looked like a regression in 5 of 6 runs with the plugin. The diffs were identical to the passing runs. The cause was a stale .pyc: the fix changed &lt;code&gt;==&lt;/code&gt; to &lt;code&gt;!=&lt;/code&gt;, which keeps the file size, and the bytecode written by the pre-fix test run still passed Python's mtime check. The scorer now clears bytecode before checking, and both arms were rescored. If you score code produced by agents, clear &lt;strong&gt;pycache&lt;/strong&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in the box
&lt;/h2&gt;

&lt;p&gt;A compaction guard that writes a handoff note before an automatic compaction and defers it up to 3 times when files were edited after the last test run. The weekly model list refresh is a GitHub Action.&lt;/p&gt;

&lt;p&gt;Repo, raw runs and the scorer: &lt;a href="https://github.com/Arthur031221/Token-Beggar" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Token-Beggar&lt;/a&gt; (MIT).&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>A seating planner that names the rules that clash, with a SAT solver in TypeScript</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Thu, 22 Oct 2026 07:07:01 +0000</pubDate>
      <link>https://dev.to/arthur031221/a-seating-planner-that-names-the-rules-that-clash-with-a-sat-solver-in-typescript-49n5</link>
      <guid>https://dev.to/arthur031221/a-seating-planner-that-names-the-rules-that-clash-with-a-sat-solver-in-typescript-49n5</guid>
      <description>&lt;p&gt;When a teacher's seating requests can't all hold, most planners say "no solution". That's true and useless. I wanted a tool that names the few requests that clash, so I wrote SeatReason, a static web app that runs entirely in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8etbfcj56mef7ylyy0q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8etbfcj56mef7ylyy0q.gif" alt="A conflict in the example class, a repaired plan with changed seats outlined, then a hand-made move that breaks a fixed seat and is undone" width="200" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three requests
&lt;/h2&gt;

&lt;p&gt;The room is a grid with optional blocked seats, up to 40 pupils and 48 seats. A request is one of three kinds: a fixed seat, a place in the first N rows, or two pupils who must not sit next to each other. That is deliberately small, so every request can be explained in one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the clash is found
&lt;/h2&gt;

&lt;p&gt;Fixed seats and front rows are a matching problem, so a Hall violator gives the clash quickly. Everything else goes to a CDCL SAT solver I wrote in TypeScript. Each request has a selector literal that guards its clauses. If the formula is unsatisfiable under all selectors, the failed assumptions are a conflict. A deletion pass then tries to drop each request from the set and keeps it only if the rest become satisfiable.&lt;/p&gt;

&lt;p&gt;The result is a subset-minimal set: take away any one request and the others can hold. The page only says "minimal" after that pass finished. If the solver runs out of its budget, the answer is "inconclusive", never "impossible".&lt;/p&gt;

&lt;h2&gt;
  
  
  Repairs you can check
&lt;/h2&gt;

&lt;p&gt;Under each request in the clash there is a preview. It plans again with that request off, keeping the current seats where it can, and checks every remaining request together. Changed seats are outlined. The plan is replaced only when you press the button, and Undo brings it back.&lt;/p&gt;

&lt;p&gt;A separate checker that doesn't share the solver's encoding validates every plan. In the tests the solver is compared with an exhaustive search on small rooms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measurements
&lt;/h2&gt;

&lt;p&gt;On an AMD Ryzen 5 7500F with Node 24.21, five runs of the bench gave a median of 18 to 32 ms to find a minimal clash that needs the SAT solver, with the slowest case at 0.38 s. Repair previews for those cases took a median of 0.26 to 0.49 s. The machine was busy during those runs, so the numbers moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Nine pupils who must all be kept apart in a 4 by 4 room run past the 8 second limit, and the page says so. Keep apart means orthogonal neighbours only. There is no gender balance, group seating or rotation.&lt;/p&gt;

&lt;p&gt;The app has nine interface languages, a JSON backup and a printable chart. Try it at &lt;a href="https://arthur031221.github.io/SeatReason/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/SeatReason/&lt;/a&gt; or read the code at &lt;a href="https://github.com/Arthur031221/SeatReason" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/SeatReason&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Matching rescanned pages to a damaged PDF with SIFT, and admitting when it can't</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:07:02 +0000</pubDate>
      <link>https://dev.to/arthur031221/matching-rescanned-pages-to-a-damaged-pdf-with-sift-and-admitting-when-it-cant-db2</link>
      <guid>https://dev.to/arthur031221/matching-rescanned-pages-to-a-damaged-pdf-with-sift-and-admitting-when-it-cant-db2</guid>
      <description>&lt;p&gt;I had a scanned PDF with a few pages ruined by a coffee ring and a crease, plus fresh scans of those pages in no particular order. So I built Darnleaf, a local tool that suggests which page each rescan replaces and lets me check the evidence before it writes anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6e05nub156m4q9pdnanv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6e05nub156m4q9pdnanv.gif" alt="Darnleaf matching seven rescans to a 40-page booklet" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching
&lt;/h2&gt;

&lt;p&gt;Every page is rendered with PDFium and reduced to a working size. A cheap pass uses a spread-out subset of SIFT features to shortlist ten candidate pages per rescan. Each candidate is then verified with RANSAC under a similarity transform, and the transform has to be plausible: scale between 0.7 and 1.4, and a skew of at most 12 degrees from a right angle. That also gives the quarter turn the rescan needs. A one to one assignment resolves conflicts, so two rescans can't claim one page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saying "check"
&lt;/h2&gt;

&lt;p&gt;A match is Clear only if it has enough consistent features, enough ink agreement after alignment and a big margin over the runner-up. Otherwise it's Check. Blank pages carry no evidence, so they're always Check. When OCR is installed, text can raise or lower a score, but it can't create a match on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export
&lt;/h2&gt;

&lt;p&gt;Replaced pages are swapped in place with pikepdf. Untouched pages stay the original objects, and bookmarks and links that point at a replaced page still work. A JSON receipt records SHA-256 hashes of the original, the output and every rescan, plus the evidence for each swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;On 90 synthetic rescans over ten held-out seeds, 45 were Clear and none of those was wrong. With 45 trials the real wrong rate could still be about 6.7%. The right page was listed for 89 of 90. The original and the rescans are drawn from the same clean bitmap, so real scans will likely do worse.&lt;/p&gt;

&lt;p&gt;Demo replay: &lt;a href="https://arthur031221.github.io/Darnleaf/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Darnleaf/&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/Arthur031221/Darnleaf" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Darnleaf&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rendering a 3D terrain flyover of a GPX route in the browser</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:07:01 +0000</pubDate>
      <link>https://dev.to/arthur031221/rendering-a-3d-terrain-flyover-of-a-gpx-route-in-the-browser-2np7</link>
      <guid>https://dev.to/arthur031221/rendering-a-3d-terrain-flyover-of-a-gpx-route-in-the-browser-2np7</guid>
      <description>&lt;p&gt;I wanted a flyover video of a hike, and every tool I found wanted me to upload the track or pay for longer clips. So I built Ridgereel, a browser app that turns a GPX, TCX or FIT file into a 3D terrain flyover MP4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcffzlmucqsk6xanz1t7o.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcffzlmucqsk6xanz1t7o.gif" alt="Ridgereel flyover of the Yushan Main Peak trail" width="252" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;A Web Worker parses the file into a track. The route is cleaned of GPS jumps and resampled about every 5 m. Elevation tiles in the AWS Terrarium encoding are decoded into one height field, and imagery from Esri, OpenTopoMap or CARTO is stitched into a single texture.&lt;/p&gt;

&lt;p&gt;Three.js draws the terrain with a camera relative sun, contour lines from &lt;code&gt;fwidth&lt;/code&gt; in the shader, fog and an edge fade. The route is three opaque line layers. A 2D overlay adds the stats and the elevation profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frames as a function of time
&lt;/h2&gt;

&lt;p&gt;I prefetch all tiles before rendering, so a frame depends only on the time. Two things follow. Seeking back to the same time redraws identical pixels, which an end to end test checks in one session. And WebCodecs can encode an exact MP4 at 30 or 60 fps instead of recording the screen in real time. Browsers without WebCodecs fall back to a real time recording.&lt;/p&gt;

&lt;p&gt;The camera rig has follow, drone and overview presets, each with an intro, a main orbit and an outro. In 9:16 the outro fits the whole route using the narrower horizontal field of view. That took one fix after the first render cut the route off at the edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy
&lt;/h2&gt;

&lt;p&gt;The file is never uploaded. A Playwright test loads a file whose name contains a marker, records every request, and fails if one goes anywhere but the four tile servers, isn't a GET, or carries the marker. Tile servers still see the area you open. By default the first and last 200 m of the route are cut and the map around them is pixelated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers and limits
&lt;/h2&gt;

&lt;p&gt;The 15 s, 1080 x 1920 sample is 450 frames. It rendered in 236 s in headless Chrome 149 on a 3 core Linux machine with software WebGL, and the file is 12.8 MB. I haven't timed a GPU. I tested export in Chrome only. The sample route uses OpenStreetMap geometry with synthetic timestamps, and the file says so.&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://arthur031221.github.io/Ridgereel/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Ridgereel/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source and docs: &lt;a href="https://github.com/Arthur031221/Ridgereel" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Ridgereel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>showdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built a desktop buddy for Claude Code that explains whatever you Ctrl-click</title>
      <dc:creator>Arthur031221</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:15:01 +0000</pubDate>
      <link>https://dev.to/arthur031221/i-built-a-desktop-buddy-for-claude-code-that-explains-whatever-you-ctrl-click-4k85</link>
      <guid>https://dev.to/arthur031221/i-built-a-desktop-buddy-for-claude-code-that-explains-whatever-you-ctrl-click-4k85</guid>
      <description>&lt;p&gt;Say you're reading a dense paragraph in a paper and you want Claude to explain it. The usual route is a screenshot, a switch to the terminal and a paste. Some pages won't even take a link, so there's no shortcut there either.&lt;/p&gt;

&lt;p&gt;Blinkling is my attempt to remove those steps. It's a Claude Code plugin that puts a small green character called Blink in the corner of your screen, above every app. Hold Ctrl (Control on a Mac), click a paragraph, a button or a picture, and the pick flies into Blink. A chat card opens beside Blink and the answer streams in. Drag instead of click to take an area.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdq05yly71sv58uwplhn0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdq05yly71sv58uwplhn0.gif" alt="Ctrl-click a paragraph and the answer streams into the chat card beside Blink"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's MIT licensed and has no API key or account of its own. Here's how the pieces fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the current version
&lt;/h2&gt;

&lt;p&gt;Version 3.2.7 is the latest. The model, effort and permission pickers on the chat card all work. Auto explain is now a short muttering bubble beside Blink, one or two sentences in your language, and it no longer opens the card. Guide mode puts marks on the page itself with a line of text under each, and Next and Previous scroll to them like links. Blink shows a thinking animation only while the AI is working. Capture is faster, and the chat card closes and Blink turns half transparent while it happens, so the card is never in the shot.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it talks to Claude Code
&lt;/h2&gt;

&lt;p&gt;There are two parts. A desktop app, an Electron shell, draws Blink and does the looking. Claude Code, signed in on the same computer, does the answering.&lt;/p&gt;

&lt;p&gt;Every capture, quick ask, typed message and guide step goes through one session. That session runs the signed-in &lt;code&gt;claude&lt;/code&gt; command without a shell and reads the answer as a stream of events, so Blink can show sentences as they arrive. Send, stop, retry and resume all work on that one conversation.&lt;/p&gt;

&lt;p&gt;A capture is held in memory until it's been handed to Claude Code, then dropped. Nothing about it is written to the app's data folder. Answers can carry marks in screen coordinates, like a highlight on the thing being explained. The overlay checks that the screen hasn't moved before it draws them, and the model returns data, never HTML or script. Markdown in answers is sanitized before display.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plugin side
&lt;/h2&gt;

&lt;p&gt;The plugin's &lt;code&gt;.mcp.json&lt;/code&gt; starts a small local bridge, &lt;code&gt;bin/blinkling.cjs&lt;/code&gt;. It listens on a loopback port (43827 by default, scanning ten) and only lets the optional Chrome and Edge helper in when that helper proves it holds a secret stored locally. The same bridge is an MCP server for Claude Code, so Claude Code can look at the screen when you ask it to. One of its tools samples the screen every 5 to 60 seconds, keeps only the frames that changed and makes no model calls.&lt;/p&gt;

&lt;p&gt;The desktop app itself is downloaded by a launcher the first time you run &lt;code&gt;/blinkling:blinkling&lt;/code&gt;. The launcher checks the build against a SHA-256 digest pinned in the plugin and installs it atomically. A build whose digest doesn't match is never installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the mirroring works
&lt;/h2&gt;

&lt;p&gt;The chat card and the terminal mirror each other, like Remote Control. Half of that is easy. A prompt you type in the terminal session that started Blink, and its finished answer, appear in Blink's chat on their own.&lt;/p&gt;

&lt;p&gt;The other half, Blink's chat showing up in the terminal, uses Claude Code's experimental channels, which only load at startup. So you start Claude Code with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--dangerously-load-development-channels&lt;/span&gt; plugin:blinkling@blinkling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code asks you to confirm once. The flag name is Claude Code's, and it reads scarier than it is. If you skip it, Blink still works and answers from its own session, you just lose the mirror in that direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;Blink only looks and explains. It never clicks, types or controls another app. It refuses to capture while a password field, a password manager or a private window has focus, and the browser helper blanks password and payment fields before anything leaves the page. There's no telemetry and no answer service run by me. What you pick goes to your own Claude Code, so your provider's terms and usage apply.&lt;/p&gt;

&lt;p&gt;Some pages are off limits to any browser extension, like browser settings, extension stores and the built-in PDF viewer. On those, Blink answers from the screen alone. Claude Code on a remote host isn't supported, because Blink and Claude Code must share a computer with a screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin marketplace add Arthur031221/Blinkling &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; claude plugin &lt;span class="nb"&gt;install &lt;/span&gt;blinkling@blinkling
claude &lt;span class="nt"&gt;--dangerously-load-development-channels&lt;/span&gt; plugin:blinkling@blinkling
/blinkling:blinkling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need Claude Code, signed in. On a Mac, allow Screen Recording once in System Settings, Privacy &amp;amp; Security, and Accessibility once so the Ctrl key can start a pick. The app isn't notarized by Apple and has no paid Windows certificate, so the installer removes the quarantine flag and signs ad hoc, but macOS may still make you choose Open Anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it with Claude Code
&lt;/h2&gt;

&lt;p&gt;The repo's first commit is from 2026-10-12, and it's past 630 commits now. The 3.x releases were built by Claude Code in long builder passes, mostly Sonnet 5.5 with Opus 5.5 asked for the hard calls. I direct it and test by hand on a Mac. The demo clips were recorded on Linux in a virtual display, so the Mac is where I look for what those clips miss.&lt;/p&gt;

&lt;p&gt;The most useful thing I learned is not to trust the builder's own verdict. A green verify report from the build isn't a result for me. Every release waits for a separate Claude Code session that downloads it, runs it under a virtual display and checks each point again before I call anything fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;It works, with limits. I only test by hand on a Mac, so Windows and Linux builds exist but have had less real use.&lt;/p&gt;

&lt;p&gt;If you know a nicer way than the channels flag to get a plugin's chat into a live Claude Code session, I'd like to hear it in the comments.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Arthur031221/Blinkling" rel="noopener noreferrer"&gt;https://github.com/Arthur031221/Blinkling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo page with the install steps animated: &lt;a href="https://arthur031221.github.io/Blinkling/" rel="noopener noreferrer"&gt;https://arthur031221.github.io/Blinkling/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>claudecode</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
