<?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: Peter Kim Frank</title>
    <description>The latest articles on DEV Community by Peter Kim Frank (@peter).</description>
    <link>https://dev.to/peter</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%2F1075%2F1c1975ce-97e8-401f-b99f-1ea88f9cae3e.jpeg</url>
      <title>DEV Community: Peter Kim Frank</title>
      <link>https://dev.to/peter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peter"/>
    <language>en</language>
    <item>
      <title>This video is about how this video was made</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Thu, 24 Sep 2026 19:54:35 +0000</pubDate>
      <link>https://dev.to/peter/this-video-is-about-how-this-video-was-made-42hl</link>
      <guid>https://dev.to/peter/this-video-is-about-how-this-video-was-made-42hl</guid>
      <description>&lt;p&gt;This video (the script, the voice timings, the source code, the storyboard, the briefs the subagents got, the grade a reviewer gave an earlier draft) is the real artifact from making &lt;em&gt;this video&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Claude Opus 5.5 built it in Claude Code, starting from an open-source project, and this post walks through the innards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seed: a video is a function of time
&lt;/h2&gt;

&lt;p&gt;It started from &lt;a href="https://github.com/JohnHeibel/PDoomVideo" rel="noopener noreferrer"&gt;JohnHeibel/PDoomVideo&lt;/a&gt;, the source of a music video Opus 5.5 made entirely in code: p5.js, a studio page, a &lt;code&gt;render.mjs&lt;/code&gt; that drives headless Chrome, and a &lt;code&gt;STORYBOARD.md&lt;/code&gt; written before any chapter code.&lt;/p&gt;

&lt;p&gt;The core idea is one rule: &lt;strong&gt;every frame is a pure function of &lt;code&gt;t&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;composite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;outC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;Math.random()&lt;/code&gt;, no clock, no state carried between frames. Randomness comes from a seeded hash, the old GLSL one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;127.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;311.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;43758.5453&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one constraint buys everything else: frames can render out of order, on several browsers at once, resumably. Any single frame can be regenerated on demand, which is what lets the video show &lt;em&gt;itself&lt;/em&gt; at frame 1,365 while it's on frame 2,000-something.&lt;/p&gt;

&lt;p&gt;The first real change was a portability fix. The repo launched Chrome with &lt;code&gt;--use-angle=d3d11&lt;/code&gt; (Direct3D, Windows only). On a Mac that fails with "Error creating webgl context"; &lt;code&gt;--use-angle=metal&lt;/code&gt; plus the macOS Chrome path fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Script → voice → timeline
&lt;/h2&gt;

&lt;p&gt;The narration is &lt;code&gt;script.md&lt;/code&gt;: 15 numbered lines. Words in &lt;code&gt;{braces}&lt;/code&gt; are &lt;strong&gt;cue words&lt;/strong&gt;, where a demonstration fires.&lt;/p&gt;

&lt;p&gt;Each line goes to ElevenLabs' &lt;code&gt;with-timestamps&lt;/code&gt; endpoint, which returns the audio plus a start and end time &lt;strong&gt;for every character&lt;/strong&gt;:&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;"characters"&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="s2"&gt;"T"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"character_start_times_seconds"&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.046&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.081&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;&lt;code&gt;tools/voice.py&lt;/code&gt; turns characters into words, finds each line's &lt;code&gt;{cue}&lt;/code&gt; word, stitches the 15 clips at exact offsets, and writes &lt;code&gt;timeline.js&lt;/code&gt;. The flash in the video fires on the frame where the word "now" is spoken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cueT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;cue&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;cue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// when line n's {cue} word is spoken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For music, the same slot is filled differently. Suno's &lt;code&gt;.m4a&lt;/code&gt; exports carry a hidden &lt;code&gt;mov_text&lt;/code&gt; subtitle stream with every lyric line timestamped (&lt;code&gt;ffmpeg -map 0:s:0 subs.srt&lt;/code&gt;); Whisper large-v3-turbo adds word times; &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt; aligns what Whisper heard to the real lyrics; and a comb search over the onset envelope finds the beat grid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storyboard before code, then subagents
&lt;/h2&gt;

&lt;p&gt;Before any chapter code, Opus wrote &lt;code&gt;STORYBOARD.md&lt;/code&gt; (one row per script line: the shot and the demonstration on the cue word) and &lt;code&gt;ANIMATION_GUIDE.md&lt;/code&gt;, the contract every builder follows: the API, the "every example is itself" rule, legibility, and the hand-off frames between chapters.&lt;/p&gt;

&lt;p&gt;Then it handed three chapters to three subagents in parallel, each with a short brief. The brief for chapter B is shown &lt;em&gt;in chapter B&lt;/em&gt;. Each subagent verified its own work with contact sheets and reported back; those reports are in the repo too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The critic
&lt;/h2&gt;

&lt;p&gt;A reviewer agent graded draft 1. The specific frame line 11 talks about got &lt;strong&gt;4/10&lt;/strong&gt;: "the side being graded is a placeholder, so the frame promises a review it can't show yet." It also caught that the HUD said &lt;code&gt;76.033 s&lt;/code&gt; when the target was &lt;code&gt;76.008 s&lt;/code&gt;. Those notes went back to the builders, and the final video shows the real draft-1 frame beside the fixed one, annotated with those exact notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blind spot
&lt;/h2&gt;

&lt;p&gt;Partway through this project, the model couldn't see images at all: every screenshot came back &lt;code&gt;(media removed — rejected by API)&lt;/code&gt;. So it checked its own frames two other ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pixel math:&lt;/strong&gt; a small script reports each frame's mean colour, bright-pixel share and the bounding box of bright content. This catches blank frames, blown-out flashes and content in the wrong place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other agents:&lt;/strong&gt; reviewer subagents, who &lt;em&gt;could&lt;/em&gt; see, described frames and scored them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The video shows both, using its own frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;render.mjs&lt;/code&gt; opens four headless Chrome workers. Each pulls the next frame index from a shared queue, calls &lt;code&gt;renderAt(i / 30)&lt;/code&gt;, and writes a JPEG. ffmpeg then stitches the frames to the narration with &lt;code&gt;libx264&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One real bug from the music-video build: with a subtitle stream as an input, &lt;code&gt;ffmpeg -shortest&lt;/code&gt; never finishes. The encode sat at 0% CPU for 20 minutes. The fix is an explicit &lt;code&gt;-t &amp;lt;duration&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ending
&lt;/h2&gt;

&lt;p&gt;The last shot is a DEV page with a player inside it, and on the word "inside" the player shows the video recursively. That isn't faked: the renderer draws its own output canvas back into the player, so it's a real Droste loop.&lt;/p&gt;

&lt;p&gt;One honest wrinkle: the video was rendered before this post existed, so that page is labelled "DRAFT · the real post comes later." You're reading the real one now.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related reading on DEV:&lt;/em&gt; &lt;a href="https://dev.to/dsplce-co/htmlcss-animation-to-video-mp4-the-headless-deterministic-way-incl-claude-4a2"&gt;HTML/CSS Animation to Video (MP4): the Headless, Deterministic Way&lt;/a&gt; · &lt;a href="https://dev.to/dhseadev/i-asked-an-agent-to-make-a-product-video-it-wrote-html-and-rendered-an-mp4-dota-companion-1nia"&gt;I asked an agent to make a product video. It wrote HTML and rendered an MP4&lt;/a&gt; · &lt;a href="https://dev.to/hiper2d/how-to-make-ai-videos-production-diary-of-a-one-minute-film-450"&gt;How to make AI videos: production diary of a one-minute film&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The video above is draft 2: 3,224 frames at 30 fps, 107.5 s. The 4/10 was the reviewer's grade on draft 1.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>video</category>
      <category>javascript</category>
    </item>
    <item>
      <title>GMail, please let me hide delegated accounts on my iPhone</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Fri, 18 Sep 2026 13:09:19 +0000</pubDate>
      <link>https://dev.to/peter/gmail-please-let-me-hide-delegated-accounts-on-my-iphone-30e</link>
      <guid>https://dev.to/peter/gmail-please-let-me-hide-delegated-accounts-on-my-iphone-30e</guid>
      <description>&lt;p&gt;Gmail's delegated accounts have started showing up in the app on my iPhone, and I find this incredibly annoying.&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%2F2q8s70jseuph91svfeol.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%2F2q8s70jseuph91svfeol.png" alt="gmail side-by-side showing the problem" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I still need access to those mailboxes. I just don't want them on my phone.&lt;/strong&gt; There should be a setting for that.&lt;/p&gt;

&lt;p&gt;Instead, I can't find a way to hide them while keeping the delegation I need. A useful work permission has become an unwanted change to how I use email on iOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding a mailbox shouldn't cost me access
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://support.google.com/mail/answer/138350?co=GENIE.Platform%3DiOS" rel="noopener noreferrer"&gt;Google's iPhone and iPad instructions&lt;/a&gt; explain how to open a delegated inbox from the account switcher. They also explain how an owner can remove a delegate. They don't document a way to hide a delegated mailbox on a particular device while preserving access.&lt;/p&gt;

&lt;p&gt;Revoking delegation is a ridiculous price to pay for cleaning up a mobile app.&lt;/p&gt;

&lt;h2&gt;
  
  
  When “All inboxes” becomes too many inboxes
&lt;/h2&gt;

&lt;p&gt;The frustration is bigger than a crowded account menu. In the Reddit thread &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/" rel="noopener noreferrer"&gt;“Removing delegated accounts from app”&lt;/a&gt;, people describe delegated messages turning up in “All inboxes” and complicating search. The original poster says finding things like tickets and return shipping codes became harder. &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/ockd6nx/" rel="noopener noreferrer"&gt;Another commenter&lt;/a&gt; describes receiving duplicate emails across accounts and then getting confusing search results.&lt;/p&gt;

&lt;p&gt;To be clear about the platforms: my complaint is about Gmail on iOS. The Reddit post began with an Android report, and many replies don't specify their device. Those reports describe related Gmail mobile frustrations; they aren't all evidence about the iPhone app.&lt;/p&gt;

&lt;p&gt;One &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/p69tsvs/" rel="noopener noreferrer"&gt;August commenter&lt;/a&gt; says roughly ten delegated accounts appeared, even though they only cover those mailboxes during colleagues' leave. On &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/p9jm60g/" rel="noopener noreferrer"&gt;September 13&lt;/a&gt;, another reports “All inboxes” being flooded with 200–300 emails daily and considers moving work or personal email to another app. These are individual reports, but they make the cost of the missing setting pretty concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thread is getting busy again
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nineteen of the thread's 32 comments arrived in August and September.&lt;/strong&gt; The daily view makes that renewed activity easier to see:&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%2Fquickchart.io%2Fchart%3Fwidth%3D800%26height%3D260%26devicePixelRatio%3D1%26format%3Dpng%26version%3D4%26backgroundColor%3Dwhite%26c%3D%257B%2522type%2522%253A%2522bar%2522%252C%2522data%2522%253A%257B%2522labels%2522%253A%255B%2522Mar%2B24%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Apr%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522May%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Jun%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Jul%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Aug%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Sep%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Sep%2B18%2522%255D%252C%2522datasets%2522%253A%255B%257B%2522data%2522%253A%255B1%252C4%252C2%252C2%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C2%252C0%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C1%252C0%252C1%252C3%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C2%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C2%252C3%252C0%252C0%252C1%252C2%252C0%252C0%252C0%252C0%255D%252C%2522backgroundColor%2522%253A%2522%25232563eb%2522%252C%2522barThickness%2522%253A3%257D%255D%257D%252C%2522options%2522%253A%257B%2522animation%2522%253Afalse%252C%2522layout%2522%253A%257B%2522padding%2522%253A%257B%2522left%2522%253A10%252C%2522right%2522%253A15%252C%2522top%2522%253A8%252C%2522bottom%2522%253A5%257D%257D%252C%2522plugins%2522%253A%257B%2522legend%2522%253A%257B%2522display%2522%253Afalse%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%2522Comments%2Bon%2Bthe%2BReddit%2Bthread%2Bby%2Bday%2522%252C%2522align%2522%253A%2522start%2522%252C%2522color%2522%253A%2522%2523172033%2522%252C%2522font%2522%253A%257B%2522size%2522%253A18%252C%2522weight%2522%253A%2522bold%2522%257D%252C%2522padding%2522%253A%257B%2522bottom%2522%253A18%257D%257D%252C%2522datalabels%2522%253A%257B%2522display%2522%253Afalse%257D%257D%252C%2522scales%2522%253A%257B%2522x%2522%253A%257B%2522grid%2522%253A%257B%2522display%2522%253Afalse%257D%252C%2522ticks%2522%253A%257B%2522autoSkip%2522%253Afalse%252C%2522maxRotation%2522%253A0%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%252C%2522color%2522%253A%2522%2523475569%2522%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%25222026%2B%255Cu00b7%2BUTC%2Bdates%2522%252C%2522color%2522%253A%2522%252364748b%2522%252C%2522font%2522%253A%257B%2522size%2522%253A11%257D%257D%257D%252C%2522y%2522%253A%257B%2522min%2522%253A0%252C%2522max%2522%253A4%252C%2522ticks%2522%253A%257B%2522stepSize%2522%253A1%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%252C%2522color%2522%253A%2522%2523475569%2522%257D%252C%2522grid%2522%253A%257B%2522color%2522%253A%2522%2523e2e8f0%2522%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%2522Comments%2522%252C%2522color%2522%253A%2522%2523475569%2522%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%257D%257D%257D%257D%257D" 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%2Fquickchart.io%2Fchart%3Fwidth%3D800%26height%3D260%26devicePixelRatio%3D1%26format%3Dpng%26version%3D4%26backgroundColor%3Dwhite%26c%3D%257B%2522type%2522%253A%2522bar%2522%252C%2522data%2522%253A%257B%2522labels%2522%253A%255B%2522Mar%2B24%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Apr%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522May%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Jun%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Jul%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Aug%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Sep%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522%2522%252C%2522Sep%2B18%2522%255D%252C%2522datasets%2522%253A%255B%257B%2522data%2522%253A%255B1%252C4%252C2%252C2%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C2%252C0%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C1%252C0%252C1%252C3%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C2%252C0%252C1%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C0%252C1%252C2%252C3%252C0%252C0%252C1%252C2%252C0%252C0%252C0%252C0%255D%252C%2522backgroundColor%2522%253A%2522%25232563eb%2522%252C%2522barThickness%2522%253A3%257D%255D%257D%252C%2522options%2522%253A%257B%2522animation%2522%253Afalse%252C%2522layout%2522%253A%257B%2522padding%2522%253A%257B%2522left%2522%253A10%252C%2522right%2522%253A15%252C%2522top%2522%253A8%252C%2522bottom%2522%253A5%257D%257D%252C%2522plugins%2522%253A%257B%2522legend%2522%253A%257B%2522display%2522%253Afalse%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%2522Comments%2Bon%2Bthe%2BReddit%2Bthread%2Bby%2Bday%2522%252C%2522align%2522%253A%2522start%2522%252C%2522color%2522%253A%2522%2523172033%2522%252C%2522font%2522%253A%257B%2522size%2522%253A18%252C%2522weight%2522%253A%2522bold%2522%257D%252C%2522padding%2522%253A%257B%2522bottom%2522%253A18%257D%257D%252C%2522datalabels%2522%253A%257B%2522display%2522%253Afalse%257D%257D%252C%2522scales%2522%253A%257B%2522x%2522%253A%257B%2522grid%2522%253A%257B%2522display%2522%253Afalse%257D%252C%2522ticks%2522%253A%257B%2522autoSkip%2522%253Afalse%252C%2522maxRotation%2522%253A0%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%252C%2522color%2522%253A%2522%2523475569%2522%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%25222026%2B%255Cu00b7%2BUTC%2Bdates%2522%252C%2522color%2522%253A%2522%252364748b%2522%252C%2522font%2522%253A%257B%2522size%2522%253A11%257D%257D%257D%252C%2522y%2522%253A%257B%2522min%2522%253A0%252C%2522max%2522%253A4%252C%2522ticks%2522%253A%257B%2522stepSize%2522%253A1%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%252C%2522color%2522%253A%2522%2523475569%2522%257D%252C%2522grid%2522%253A%257B%2522color%2522%253A%2522%2523e2e8f0%2522%257D%252C%2522title%2522%253A%257B%2522display%2522%253Atrue%252C%2522text%2522%253A%2522Comments%2522%252C%2522color%2522%253A%2522%2523475569%2522%252C%2522font%2522%253A%257B%2522size%2522%253A12%257D%257D%257D%257D%257D%257D" alt="Daily comments from March 24 to September 18, 2026: an initial March burst, a long quiet stretch, and renewed activity in August and September." width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one 32-comment Reddit thread, not a representative survey; September is partial through September 18, 2026.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comments on the Reddit thread by day. Source: &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/" rel="noopener noreferrer"&gt;the discussion&lt;/a&gt;, fetched September 18. Counts include all comments and replies by UTC date, with zero-activity days included; the latest comment was September 14.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These are activity counts, not unique people or classified complaints. Most recent replies describe unwanted accounts or disruption, with repeated requests for an off switch; some earlier commenters welcomed mobile delegation.&lt;/p&gt;

&lt;p&gt;What's particularly irritating is the recurring hope that it's gone. Several people say the accounts disappeared, then returned. On August 14, &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/p3p219z/" rel="noopener noreferrer"&gt;one commenter wrote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I had them, and then they disappeared and I was so happy. AND NOW THEY'RE BACK. 😭&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That doesn't establish why the behavior changed. It does explain why people keep returning to ask whether anyone has found an off switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give us an off switch that preserves delegation
&lt;/h2&gt;

&lt;p&gt;I understand wanting delegated mail on a phone. Someone covering a shared inbox may be delighted to have it. A sysadmin in the thread &lt;a href="https://www.reddit.com/r/GMail/comments/1s20si1/removing_delegated_accounts_from_app/p4bsw1m/" rel="noopener noreferrer"&gt;makes exactly that point while asking for a toggle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Google, &lt;strong&gt;let us hide or disable delegated mailboxes in the mobile app without revoking delegation.&lt;/strong&gt; Let us choose which ones appear on each device. Let us exclude them from “All inboxes” and cross-account search. &lt;strong&gt;Keep the underlying delegation intact&lt;/strong&gt;, so hiding a mailbox on my iPhone doesn't mean losing access when I sit down to work.&lt;/p&gt;

&lt;p&gt;Having permission to open a mailbox should come with the freedom to leave it off my phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yes, this post is for search engines
&lt;/h2&gt;

&lt;p&gt;Yes, I'm partly writing this for search engines. If you got here searching &lt;strong&gt;how to remove or hide delegated Gmail accounts on iPhone or iOS&lt;/strong&gt;, you're not the only one frustrated by this.&lt;/p&gt;

&lt;p&gt;I'd like this to be a place to vent, compare notes, and share working solutions when they turn up. Vent below. And if you've found something that actually hides these mailboxes without revoking delegation, please share it, including your device and app version.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>design</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>What is current your "daily driver" coding model? (July 2026)</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Wed, 29 Jul 2026 14:29:13 +0000</pubDate>
      <link>https://dev.to/peter/what-is-current-your-daily-driver-coding-model-july-2026-24cl</link>
      <guid>https://dev.to/peter/what-is-current-your-daily-driver-coding-model-july-2026-24cl</guid>
      <description>&lt;p&gt;Fable 5 at Max effort?  OpenAI's gpt-5.6?  The latest Gemini models?  Perhaps Kimi K3 or other open-weight models?&lt;/p&gt;

&lt;p&gt;For your typical needs these days, what model do you tend to use and why?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Converting old home movie DVDs into a private streaming site</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Wed, 08 Apr 2026 19:46:46 +0000</pubDate>
      <link>https://dev.to/peter/converting-old-home-movie-dvds-into-a-private-streaming-site-5bmb</link>
      <guid>https://dev.to/peter/converting-old-home-movie-dvds-into-a-private-streaming-site-5bmb</guid>
      <description>&lt;p&gt;A relative sent me a few DVDs containing &lt;em&gt;really old&lt;/em&gt; home videos that were at one point saved to DVDs.  The problem is, I don't own a DVD player, and neither do most people in 2026.  These discs were just sitting in a box, slowly becoming unplayable, holding memories nobody could actually watch (which is a real shame).&lt;/p&gt;

&lt;p&gt;So I bought a cheap USB DVD drive on Amazon (~$25, an Amicool A11), and figured I'd rip the discs and put the videos somewhere my family could get to them. I'm a vibe coder at this point so I used Claude Code and put together a workflow to rip the discs and upload the videos to private streaming site my whole family can pull up on their phones.&lt;/p&gt;

&lt;p&gt;Here's how we did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;On the software side, everything is free CLI tools installed through Homebrew: &lt;code&gt;ddrescue&lt;/code&gt; for ripping, &lt;code&gt;ffmpeg&lt;/code&gt; for video conversion, and &lt;code&gt;wrangler&lt;/code&gt; for uploading to Cloudflare. Claude Code handled the commands — I mostly described what I wanted and swapped discs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: make a safe copy of each disc
&lt;/h2&gt;

&lt;p&gt;The first thing Claude Code suggested was making a bit-perfect copy of each disc before doing anything else. That way even if a disc gets scratched later, we have an exact digital duplicate.&lt;/p&gt;

&lt;p&gt;The tool for this is &lt;code&gt;ddrescue&lt;/code&gt; (not &lt;code&gt;dd&lt;/code&gt;). The difference: if a disc has scratches or bad spots, &lt;code&gt;dd&lt;/code&gt; just fails. &lt;code&gt;ddrescue&lt;/code&gt; is smarter — it copies what it can, then goes back and retries the problem areas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ddrescue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There were a couple of macOS-specific gotchas that Claude Code figured out along the way. The disc drive doesn't show up in &lt;code&gt;diskutil list&lt;/code&gt; like a normal drive — you need &lt;code&gt;drutil status&lt;/code&gt; to detect it. And ddrescue needs to be told the exact disc size, otherwise it thinks there's 9,223 petabytes of data to read (basically infinity). The size comes from the block count that &lt;code&gt;drutil&lt;/code&gt; reports.&lt;/p&gt;

&lt;p&gt;We wrapped all of this into a script so I could just run &lt;code&gt;./rip.sh disc-01&lt;/code&gt;, wait about 10 minutes, swap the disc, and repeat. Each disc produced a ~4.3 GB &lt;code&gt;.iso&lt;/code&gt; file — a perfect clone that I can keep forever as an archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: convert to something playable
&lt;/h2&gt;

&lt;p&gt;Mount the ISO and you find a &lt;code&gt;VIDEO_TS&lt;/code&gt; folder full of &lt;code&gt;.VOB&lt;/code&gt; files. That's the old DVD-Video format — fine for DVD players, useless for phones and browsers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffmpeg&lt;/code&gt; converts each clip to a modern H.264 MP4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; VTS_01_1.VOB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  clip-01.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't know any of these flags — Claude Code picked the settings and explained why each one matters (the &lt;code&gt;-movflags +faststart&lt;/code&gt; one moves metadata to the front of the file so browsers can stream it without downloading the whole thing first). My discs had 13 to 21 clips each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: put it online
&lt;/h2&gt;

&lt;p&gt;You could upload these to YouTube (unlisted), Google Drive, or Dropbox and call it a day. But I liked the idea of having my own thing — files on storage I control, a simple site I can customize, no platform deciding to compress my videos or change their sharing features.&lt;/p&gt;

&lt;p&gt;Claude Code set up the hosting on Cloudflare, which turned out to be free for this use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare R2&lt;/strong&gt; stores the video files. It's object storage, like a hard drive in the cloud. You upload files with a CLI command and they just sit there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; hosts a simple website — one HTML file with a grid of video thumbnails.&lt;/li&gt;
&lt;li&gt;A small &lt;strong&gt;Pages Function&lt;/strong&gt; connects the two, serving videos from R2 when you click play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The upload was just a loop running &lt;code&gt;wrangler r2 object put&lt;/code&gt; for each clip. Claude Code handled the commands while I did other things.&lt;/p&gt;

&lt;p&gt;The one step I had to do manually in the Cloudflare dashboard was connecting the R2 bucket to the Pages site. Under the project's Settings &amp;gt; Bindings, I added an R2 binding so the site's code could actually read from the bucket. Took about 30 seconds but it's not something the CLI can do yet.&lt;/p&gt;

&lt;p&gt;To lock it down to family only, there's &lt;strong&gt;Cloudflare Access&lt;/strong&gt; — you add email addresses of people who should have access, and everyone else gets a login screen. Also free for small groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gallery
&lt;/h2&gt;

&lt;p&gt;The site itself is a single HTML page. No React, no build step. Just a dark grid of thumbnails.&lt;/p&gt;

&lt;p&gt;Claude Code added a couple of nice touches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lightweight thumbnails&lt;/strong&gt; — instead of loading 47 video players on page load, each clip shows a tiny JPEG thumbnail (~5 KB) with a play button. Click it and the actual video loads. The page loads in under a second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hover preview&lt;/strong&gt; — move your mouse across any thumbnail and it scrubs through the video, YouTube-style. This is done with sprite sheets — one image per clip containing 20 frames tiled horizontally (~40 KB each). No video data loads until you actually click play.&lt;/p&gt;

&lt;p&gt;There's a small progress bar at the bottom of each thumbnail as you scrub so you can tell where you are in the clip.&lt;/p&gt;

&lt;p&gt;The video manifest is just a JavaScript object that maps disc names to clip counts. When I rip a new disc, I add one line and redeploy.&lt;/p&gt;

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

&lt;p&gt;Essentially nothing. Cloudflare R2 doesn't charge for bandwidth — when family streams a video, there's no per-GB fee. Storage is free up to 10 GB, and my 3-disc archive is about 8 GB of converted video. Even if I rip every disc in the box I'd be well under $1/month. For a family casually rewatching old home videos, this is basically a free setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rip script
&lt;/h2&gt;

&lt;p&gt;Here's the script Claude Code and I ended up with. It auto-detects the disc, figures out the size, and runs a two-pass rip:&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="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-lt&lt;/span&gt; 1 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;disc-label&amp;gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;RAW_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/raw"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/logs"&lt;/span&gt;

&lt;span class="nv"&gt;DISC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;drutil status 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Name:"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $NF}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No disc detected. Insert a disc and try again."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;BLOCKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;drutil status 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Space Used:"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/.*blocks:[[:space:]]*//'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DISC_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; BLOCKS &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;

diskutil unmountDisk &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true

&lt;/span&gt;ddrescue &lt;span class="nt"&gt;-b&lt;/span&gt; 2048 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC_SIZE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iso"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;
ddrescue &lt;span class="nt"&gt;-b&lt;/span&gt; 2048 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC_SIZE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 3 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DISC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iso"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.log"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done! &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.iso"&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $5}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pop a disc in, run &lt;code&gt;./rip.sh disc-04&lt;/code&gt;, swap, repeat. About 10 minutes per disc.&lt;/p&gt;




&lt;p&gt;The whole project took about an hour of actual thinking work, and a few hours of waiting for discs to rip and videos to upload while I did other things. Claude Code handled the parts I didn't know — the ffmpeg flags, the Cloudflare Pages Function for video seeking, the sprite sheet math — and I handled the part it couldn't: physically swapping discs.&lt;/p&gt;

&lt;p&gt;My mom can now watch her old home videos on her phone. That alone was worth it.  And I can rest easy knowing these family memories are safely stored in the cloud.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>cloudflare</category>
      <category>dvd</category>
      <category>ripping</category>
    </item>
    <item>
      <title>Sorting algorithms visualized</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Mon, 23 Feb 2026 19:48:11 +0000</pubDate>
      <link>https://dev.to/peter/sorting-algorithms-visualized-5oh</link>
      <guid>https://dev.to/peter/sorting-algorithms-visualized-5oh</guid>
      <description>&lt;p&gt;Wanted an excuse to play with the new &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-1-pro/" rel="noopener noreferrer"&gt;Gemini 3.1 Pro&lt;/a&gt; so I vibe-coded this sorting visualization playground and deployed it to Cloud Run.&lt;/p&gt;

&lt;p&gt;I always thought &lt;a href="https://www.youtube.com/watch?v=kPRA0W1kECg" rel="noopener noreferrer"&gt;these sorts of videos were fun&lt;/a&gt; so it felt cool to sort of build my own.&lt;/p&gt;


&lt;div class="ltag__cloud-run"&gt;
  &lt;iframe width="100%" height="600px" src="https://sorting-visualizer-584800428475.us-west1.run.app"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


</description>
      <category>sorting</category>
      <category>visualization</category>
      <category>data</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Setting up a public URL that flashes my office lights</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Mon, 05 Jan 2026 18:38:28 +0000</pubDate>
      <link>https://dev.to/peter/setting-up-a-public-url-that-flashes-my-office-lights-218i</link>
      <guid>https://dev.to/peter/setting-up-a-public-url-that-flashes-my-office-lights-218i</guid>
      <description>&lt;p&gt;I've been running Home Assistant on a Raspberry Pi for over two years. It controls my Hue lights, Zigbee devices, the usual stuff. It's always been a local network thing.&lt;/p&gt;

&lt;p&gt;As every vibecoder knows, you've got it working on &lt;a href="https://localhost:3000" rel="noopener noreferrer"&gt;https://localhost:3000&lt;/a&gt; but that doesn't mean you can show your friends type of thing.&lt;/p&gt;

&lt;p&gt;I had this little home network but I never wanted to expose it to the broader internet.&lt;/p&gt;

&lt;p&gt;This past weekend, I spun up a $4/month DigitalOcean droplet to run &lt;a href="https://ntfy.sh" rel="noopener noreferrer"&gt;ntfy&lt;/a&gt;, a self-hosted push notification service. The idea was to give various automations a way to send push notifications to my phone.&lt;/p&gt;

&lt;p&gt;Then I realized: now I've got a droplet which can serve as my internet-facing endpoint.  That means I can probably gateway to trigger things on my home network via HomeAssistant&lt;/p&gt;

&lt;p&gt;So, I now have a URL that &lt;em&gt;anyone with a token can hit&lt;/em&gt; to flash my office lights red - pretty much an IRL ping that something needs my attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;My Pi is behind my home router. I don't want to port-forward or expose Home Assistant directly. But I want to trigger it from the internet.&lt;/p&gt;

&lt;p&gt;The droplet solves this. It's public-facing. The question is: how do I get from the droplet to my Pi securely?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Tailscale
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailscale.com" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; creates a mesh VPN between devices. Install it on both the droplet and the Pi, and they can talk to each other using private IPs (like &lt;code&gt;100.x.x.x&lt;/code&gt;) - no port forwarding needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet → Droplet (public) → Tailscale → Pi (private) → Home Assistant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Claude Code Built
&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://claude.ai/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; to wire this up.  My key insight was that I could simply give Claude Code SSH access to both my Pi and Droplet and let it handle a lot of the rest.&lt;/p&gt;

&lt;p&gt;I described what I wanted, and it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SSHed into my Pi and queried Home Assistant to find my light entity IDs&lt;/li&gt;
&lt;li&gt;Wrote a bash script that flashes the lights red, then restores the previous color&lt;/li&gt;
&lt;li&gt;Installed Tailscale on both the Pi and droplet&lt;/li&gt;
&lt;li&gt;Generated SSH keys so the droplet can run commands on the Pi&lt;/li&gt;
&lt;li&gt;Created a Flask webhook with token-based auth&lt;/li&gt;
&lt;li&gt;Set up nginx to route requests&lt;/li&gt;
&lt;li&gt;Created systemd services so everything survives reboots&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing took maybe 20 minutes. Most of that was waiting for apt to install packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request: GET /flash-peter-office-lights?auth_token=xxx
                    ↓
            Cloudflare (HTTPS)
                    ↓
            DigitalOcean Droplet
            nginx → Flask (port 5000)
                    ↓
            Tailscale (100.x.x.x)
                    ↓
            Raspberry Pi
            SSH → flash_lights.sh
                    ↓
            Home Assistant API
                    ↓
            Lights flash red → restore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Flash Script
&lt;/h2&gt;

&lt;p&gt;The tricky part is restoring the lights to their previous state. Home Assistant lights can be in different color modes, so the script saves the current state before flashing:&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="c"&gt;# Save current state&lt;/span&gt;
&lt;span class="nv"&gt;STATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$HA_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://localhost:8123/api/states/light.office"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;WAS_ON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STATE&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.state'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BRIGHTNESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STATE&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.attributes.brightness // 255'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;XY_X&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STATE&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.attributes.xy_color[0] // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;XY_Y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$STATE&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.attributes.xy_color[1] // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Flash red&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:8123/api/services/light/turn_on"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$HA_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"entity_id": "light.office", "rgb_color": [255, 0, 0], "brightness": 255}'&lt;/span&gt;

&lt;span class="nb"&gt;sleep &lt;/span&gt;1

&lt;span class="c"&gt;# Restore&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:8123/api/services/light/turn_on"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$HA_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;entity_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;light.office&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brightness&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$BRIGHTNESS&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;xy_color&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="nv"&gt;$XY_X&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="nv"&gt;$XY_Y&lt;/span&gt;&lt;span class="s2"&gt;]}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First version only saved brightness. When I told Claude Code "the lights aren't going back to where they were," it figured out the issue and added the &lt;code&gt;xy_color&lt;/code&gt; handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Webhook (Flask)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_tokens&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/root/webhooks/tokens.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/flash-peter-office-lights&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;auth_token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing auth_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

    &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_tokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;

    &lt;span class="c1"&gt;# SSH to Pi via Tailscale and run the flash script
&lt;/span&gt;    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ssh -i /root/.ssh/pi_key peter@100.x.x.x &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/peter/flash_lights.sh&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flashed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tokens live in a JSON file:&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;"alice-token-123"&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="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-05"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bob-token-456"&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="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-05"&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;Each person gets their own token. Revoke access by deleting their entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Now that the plumbing exists, I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Different colors for different sources&lt;/strong&gt; - blue for Slack, green for family texts, red for emergencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack slash command&lt;/strong&gt; - &lt;code&gt;/flash-peter&lt;/code&gt; for coworkers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS Shortcut&lt;/strong&gt; - one-tap button for my wife&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; - prevent abuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; - who flashed and when&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to build something similar, the pieces are: Raspberry Pi (or another device to run HomeAssistant), a cheap VPS with Tailscale on both ends, and some basic Python/bash (in my case, written by Claude).&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>raspberr</category>
      <category>tailscale</category>
      <category>automation</category>
    </item>
    <item>
      <title>Welcome Thread</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Fri, 19 Sep 2025 19:17:15 +0000</pubDate>
      <link>https://dev.to/peter/welcome-thread-n3m</link>
      <guid>https://dev.to/peter/welcome-thread-n3m</guid>
      <description>&lt;p&gt;Welcome!  What have you built recently?&lt;/p&gt;

</description>
      <category>welcome</category>
    </item>
    <item>
      <title>The new PostHog.com is pretty amazing ... most distinctive landing page I've seen in a long time.</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Thu, 11 Sep 2025 13:08:04 +0000</pubDate>
      <link>https://dev.to/peter/the-new-posthogcom-is-pretty-amazing-most-distinctive-landing-page-ive-seen-in-a-long-1kna</link>
      <guid>https://dev.to/peter/the-new-posthogcom-is-pretty-amazing-most-distinctive-landing-page-ive-seen-in-a-long-1kna</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://posthog.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fposthog.com%2Fimages%2Fog%2Fdefault.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://posthog.com/" rel="noopener noreferrer" class="c-link"&gt;
            PostHog – We make your product self-driving
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            PostHog automatically diagnoses problems, fixes bugs, and generates pull requests – all without you having to prompt it.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fposthog.com%2Ffavicon-32x32.png%3Fv%3D6e5ac8d4a5b381b5caa29396fbf7c955" width="32" height="32"&gt;
          posthog.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>design</category>
      <category>discuss</category>
      <category>startup</category>
    </item>
    <item>
      <title>What tools are you using to vibe code?</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Tue, 12 Aug 2025 00:07:53 +0000</pubDate>
      <link>https://dev.to/peter/what-tools-are-you-using-to-vibe-code-2oe1</link>
      <guid>https://dev.to/peter/what-tools-are-you-using-to-vibe-code-2oe1</guid>
      <description>&lt;p&gt;Share the tools, platforms, technologies that you're using as you vibe code.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
    </item>
    <item>
      <title>Tip: ask your agent to create a /debug page</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Fri, 11 Jul 2025 23:11:33 +0000</pubDate>
      <link>https://dev.to/peter/tip-ask-your-agent-to-create-a-debug-page-57ek</link>
      <guid>https://dev.to/peter/tip-ask-your-agent-to-create-a-debug-page-57ek</guid>
      <description>&lt;p&gt;Have been using this "trick" more and more frequently.&lt;/p&gt;

&lt;p&gt;When you're running into trouble articulating what's going right or wrong when working with a coding assistant/agent, consider asking it to build a &lt;code&gt;/debug&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;Doesn’t need to be fancy. Just a simple &lt;strong&gt;live and visual&lt;/strong&gt; canvas that shows: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the agent &lt;em&gt;thinks&lt;/em&gt; it’s doing
&lt;/li&gt;
&lt;li&gt;what inputs it’s working with
&lt;/li&gt;
&lt;li&gt;what tools are firing (or not)
&lt;/li&gt;
&lt;li&gt;any recent errors or outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are other ways to do this sort of thing (browser/server logs, MCP with tighter feedback loops, etc), but this has been a quick/dirty trick that has saved me some frustrating debugging sessions.&lt;/p&gt;

&lt;p&gt;Especially good when something’s &lt;em&gt;almost&lt;/em&gt; working and you can’t quite tell why.&lt;/p&gt;

</description>
      <category>vibecode</category>
      <category>beginners</category>
      <category>ai</category>
      <category>css</category>
    </item>
    <item>
      <title>Join the World's Largest Hackathon: $1 Million in Prizes 💸</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Fri, 30 May 2025 19:27:41 +0000</pubDate>
      <link>https://dev.to/devteam/join-the-worlds-largest-hackathon-1-million-in-prizes-3hfh</link>
      <guid>https://dev.to/devteam/join-the-worlds-largest-hackathon-1-million-in-prizes-3hfh</guid>
      <description>&lt;p&gt;Our friends at &lt;a href="https://bolt.new/" rel="noopener noreferrer"&gt;Bolt&lt;/a&gt; just officially kicked off the &lt;a href="https://hackathon.dev/" rel="noopener noreferrer"&gt;World's Largest Hackathon&lt;/a&gt;, an awesome competition with &lt;strong&gt;over $1,000,000 in prizes&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;We love that this hackathon is breaking down barriers and welcoming new participants into the ecosystem.  If you’re not yet familiar with Bolt, it’s a powerful AI-platform that enables &lt;em&gt;anyone&lt;/em&gt; with an idea to build production-ready applications without traditional coding skills. &lt;/p&gt;

&lt;p&gt;Whether you're a seasoned developer or a first-time entrepreneur with a vision, we hope you give this hackathon a try! &lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h2&gt;
  
  
  How To Participate
&lt;/h2&gt;

&lt;p&gt;Click the link below for all the details on how to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://worldslargesthackathon.devpost.com/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Join the World's Largest Hackathon&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fun Fact: Everyone who signs up for the hackathon will receive a builder pack with thousands of credits to different services - AI voice, UI gen, infra, you name it!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the Hackathon Kickoff Video:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/KY8xaxkQz0w" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;


&lt;/div&gt;


&lt;h2&gt;
  
  
  Community Support
&lt;/h2&gt;

&lt;p&gt;The main Hackathon entry process will take place via DevPost, but we’ll be supporting the World’s Largest Hackathon in a variety of ways!&lt;/p&gt;

&lt;h3&gt;
  
  
  Livestream Events
&lt;/h3&gt;

&lt;p&gt;Starting next week, we'll be simulcasting as many hackathon-related livestreams as possible directly on the DEV homepage. Here are the first three sessions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain Expertise with Entri&lt;/strong&gt; - June 2, 2025 at 9:00AM PT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;So what is an API anyways? With Pica&lt;/strong&gt; - June 3, 2025 at 9:00AM PT
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building Conversational Video Agents with Tavus and Bolt&lt;/strong&gt; - June 4, 2025 at 9:00AM PT&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Judging
&lt;/h3&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/jess"&gt;@jess&lt;/a&gt; will be bringing her judging experience from &lt;a href="https://dev.to/challenges"&gt;DEV Challenges&lt;/a&gt; to the event and will be serving as an official judge for the hackathon! She can't wait to check out all the innovative projects that emerge from this event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Contest Coming Soon
&lt;/h3&gt;

&lt;p&gt;We'll also be launching a &lt;strong&gt;writing contest specifically for World’s Largest Hackathon participants&lt;/strong&gt; later this month, so stay tuned for details on how you can share your building journey and compete for exclusive DEV badges!&lt;/p&gt;




&lt;p&gt;What are you planning to build? Drop a comment and let the community know! 👇&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>vibecoding</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Apple Magic Keyboard (USB-C) function keys not working</title>
      <dc:creator>Peter Kim Frank</dc:creator>
      <pubDate>Wed, 02 Apr 2025 10:16:00 +0000</pubDate>
      <link>https://dev.to/peter/apple-magic-keyboard-usb-c-function-keys-not-working-3kag</link>
      <guid>https://dev.to/peter/apple-magic-keyboard-usb-c-function-keys-not-working-3kag</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: I'm basically writing this post for Google because I couldn't find useful/relevant results when I was searching myself...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Specs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Macbook Pro running Sonoma 14.6.1 on Apple Silicon&lt;/li&gt;
&lt;li&gt;New keyboard is model A3118 with USB-C&lt;/li&gt;
&lt;li&gt;Old keyboard is model A2449 with Lightning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've been using a Magic Keyboard with Touch ID (lightning cable) for many, many, many years. It’s been rock-solid the entire with time — no issues, always reliable. But recently, a key on my Lightning version stuck/broke so I decided to get a replacement.  I chose a new model (A3118) with a USB-C connector instead of Lightning. &lt;/p&gt;

&lt;p&gt;Seemed like a minor upgrade—same keyboard, just with the modern connector, right?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;Right out of the box, the USB-C version had issues. It connected fine, typed fine, but none of the system-level features worked. The Touch ID sensor didn’t respond. The function keys—brightness, volume, media—didn’t do anything. I toggled every keyboard setting I could find in System Settings. Nothing changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything worked as normal&lt;/strong&gt; on my built-in native keyboard on my Macbook, so I knew it was a firmware/connection issue and not something system wide.&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.amazonaws.com%2Fuploads%2Farticles%2Flku9bxx3mu4fu1e1u9u6.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.amazonaws.com%2Fuploads%2Farticles%2Flku9bxx3mu4fu1e1u9u6.png" alt=" " width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes, I tried toggling the Function Keys in Keyboard Settings&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I tried everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restarted my MacBook Pro)&lt;/li&gt;
&lt;li&gt;Connected via USB-C directly&lt;/li&gt;
&lt;li&gt;Paired via Bluetooth&lt;/li&gt;
&lt;li&gt;Used it in wired-only mode&lt;/li&gt;
&lt;li&gt;Reset keyboard settings and Bluetooth modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still broken.&lt;/p&gt;

&lt;p&gt;To double-check it wasn’t my machine, I plugged in the old Lightning version. Everything worked perfectly—Touch ID, function keys, all the macOS integrations I’d come to expect.&lt;/p&gt;

&lt;p&gt;I brought my Macbook Pro along with the new USB-C keyboard to the Apple Store. They tested it and saw the same issues.  They then brought out their store tester keyboards (both USB-C and Lightning) and were able to confirm the issue -- demonstrating that it wasn't a specific faulty unit, but a broader issue.  Lightning worked without issue, USB-C has the broken function keys.&lt;/p&gt;

&lt;p&gt;So, yeah... if you’re considering upgrading to the new USB-C Magic Keyboard with Touch ID: maybe wait or do a bit more research. There seems to be a firmware or macOS-level compatibility issue that breaks key features.&lt;/p&gt;

&lt;p&gt;Hope this helps the next person avoid the painful Google search + troubleshooting issues.&lt;/p&gt;

</description>
      <category>apple</category>
      <category>hardware</category>
      <category>troubleshooting</category>
    </item>
  </channel>
</rss>
