<?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 Shinners</title>
    <description>The latest articles on DEV Community by Peter Shinners (@shredwheat).</description>
    <link>https://dev.to/shredwheat</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%2F2639173%2Fe54ce1f2-f591-445e-a45e-a642db80a523.jpg</url>
      <title>DEV Community: Peter Shinners</title>
      <link>https://dev.to/shredwheat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shredwheat"/>
    <language>en</language>
    <item>
      <title>2nd Thumbrella Tutorial Video Posted</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Sat, 03 Oct 2026 23:23:42 +0000</pubDate>
      <link>https://dev.to/shredwheat/2nd-thumbrella-tutorial-video-posted-1hic</link>
      <guid>https://dev.to/shredwheat/2nd-thumbrella-tutorial-video-posted-1hic</guid>
      <description>&lt;p&gt;I posted my second tutorial/intro Thumbrella video to Youtube. A simple overview of caching configurations that can be used with a self hosted &lt;a href="https://thumbrella.dev" rel="noopener noreferrer"&gt;Thumbrella&lt;/a&gt; server.&lt;/p&gt;

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

&lt;p&gt;Still fun to create (and somewhat terrifying). I'll get better. Any requests for the next topics?&lt;/p&gt;

</description>
      <category>thumbnail</category>
      <category>showdev</category>
      <category>server</category>
      <category>cache</category>
    </item>
    <item>
      <title>Tech Stack for Thumbrella Cloud</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Tue, 29 Sep 2026 03:51:14 +0000</pubDate>
      <link>https://dev.to/shredwheat/tech-stack-for-thumbrella-cloud-18fm</link>
      <guid>https://dev.to/shredwheat/tech-stack-for-thumbrella-cloud-18fm</guid>
      <description>&lt;p&gt;There's a mountain of cloud services out there, and this one is mine. This post looks at the individual services I landed on, including my thoughts and pricing strategies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thumbrella.dev" rel="noopener noreferrer"&gt;Thumbrella&lt;/a&gt; is an online server for thumbnailing media. It's fast and efficient. The server is open source and intended to be self hosted. There is also a Cloud service that provides gargantuan free usage. This article looks at the tech stack behind the scenes for the online Cloud service.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt; - Edge hosting
&lt;/h2&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%2F00mqueup83zj0cn2unlm.ico" 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%2F00mqueup83zj0cn2unlm.ico" width="99" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The primary entry point for the service is handled through &lt;a href="https://cloudflare.com" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;. I've been super happy with this choice for the project core. I'm taking advantage of KV Storage, D1 Database, Analytics Engine, rate limiting, and edge-local caching.&lt;/p&gt;

&lt;p&gt;This basic level of the service is compiled from Thumbrella's "tier1" as a WASM worker. This runs natively and fast as a Cloudflare worker. &lt;/p&gt;

&lt;p&gt;Cloudflare is a fairly common choice for many workers with similar requirements. So nothing surprising here, other than I'm super impressed with the service. Currently this is using the $5/month worker plan because I was experimenting using Cloudflare containers earlier. At this point that's not part of the service, which may cause me to scale back to the free Workers tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt; - Account management
&lt;/h2&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%2F38ut50ribnm3jo6gjup5.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%2F38ut50ribnm3jo6gjup5.png" width="100" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For all user management I've been really happy with &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt;. This handled all registration and user account management. I've integrated Clerk's user admin dashboard directly into the main Thumbrella main website, and I worked hard to make all parts of the website feel integrated.&lt;/p&gt;

&lt;p&gt;Clerk will be free to use up until 50,000 active accounts. If that kind of stratospheric growth happens I'll likely request one of their incubation plans for startups. Clerk manages all the subscriptions, credits, and details I'm happy to punt on to a service that gets the specifics correct.&lt;/p&gt;

&lt;p&gt;This also allows me to minimize my PII (personally identifiable information). The only thing Thumbrella stores are Clerk user identifiers. I don't have any emails user information to deal with or protect.&lt;/p&gt;

&lt;p&gt;I'm also hoping Clerk's account linking and bot protection will at least minimize potential for abuse. Nothing I expect to be perfect. But also I'm expecting to be thousands of times better than me rolling my own.&lt;/p&gt;

&lt;p&gt;One thing that surprised me was switching from Clerk's "development" mode to "production". They are managed as entirely separate systems. Once I was ready to switch to production I found a bit more work than I prepared for. But this also allowed some cool and deeper integrations; like routing all authentication through a subdomain (clerk.thumbrella.dev).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://modal.com/" rel="noopener noreferrer"&gt;Modal&lt;/a&gt; - Compute
&lt;/h2&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%2Fmodal.com%2Fassets%2Ffavicon.ico" 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%2Fmodal.com%2Fassets%2Ffavicon.ico" width="48" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ended up using &lt;a href="https://modal.com/" rel="noopener noreferrer"&gt;Modal&lt;/a&gt; for the actual media processing. What a pleasant surprise discovering Modal. The Thumbrella processing code (tier2 and tier3) have been super comfortable inside these containers. For my use cases I only need CPU processing, which almost flies underneath their GPU focused pricing tiers. Generally they run processing through Docker containers, and provide a magic programmatic interface for configuring and deploying them.&lt;/p&gt;

&lt;p&gt;These run quicker than I had been seeing on Cloudflare Containers and a couple random, cheap VPS services I experimented with.&lt;/p&gt;

&lt;p&gt;Modal accounts get a free $30 of processing per month, which Thumbrella fits comfortably inside of (for now). As Thumbrella usage grows I'll likely need to apply for their Starter credit grants. I'm also prepared to offload some free processing to slower economic services, like Cloudflare Containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.checklyhq.com/" rel="noopener noreferrer"&gt;Checkly&lt;/a&gt; - Service monitoring
&lt;/h2&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%2Fg8d3jxqnnq9qys7stsu1.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%2Fg8d3jxqnnq9qys7stsu1.png" width="32" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.checklyhq.com/" rel="noopener noreferrer"&gt;Checkly&lt;/a&gt; is a lightweight service uptime monitor. While they offer a wide range of alerts and AI analysis, I am only interested in small uptime tracking charts. The free service does plenty for my needs. I have the assortment of domains and services mentioned in this document all monitored by Checkly. I haven't hooked up any alerts or notifications, but do get a weekly summary email.&lt;/p&gt;

&lt;p&gt;Of all the services I've used, Checkly is the most "aggressive" at marketing and signing ups. Fortunately it was all quite easy to unsubscribe and settle down.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com" rel="noopener noreferrer"&gt;Github&lt;/a&gt; - Private container images
&lt;/h2&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%2Fu8flnbwz97kcr6mc8cvi.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%2Fu8flnbwz97kcr6mc8cvi.png" width="32" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com" rel="noopener noreferrer"&gt;Github&lt;/a&gt; hosts the open source server and client libraries. Github actions are used to build and deploy many of the various packages. There is also a private Github repository holding all the Cloudflare integrations.&lt;/p&gt;

&lt;p&gt;I also rely on Github to host private OCI Docker images which deploy to Modal. After everything else Github is used for this feels like a trivial side note.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&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%2Fu2m13dedv2164vtzuhwa.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%2Fu2m13dedv2164vtzuhwa.png" width="112" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connecting all these pieces has been surprisingly interesting. One of the primary goals was getting the Cloud service running free for as much as possible. The right services are out there if you search. Seeing what other services are doing right (and doing wrong) inspired the motivations for my own project.&lt;/p&gt;

&lt;p&gt;You can read more about the Cloud service directly on &lt;a href="https://thumbrella.dev/docs/cloud/" rel="noopener noreferrer"&gt;Thumbrella's documentation&lt;/a&gt; and the &lt;a href="https://thumbrella.dev/docs/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; gives a comparison with other services &lt;em&gt;(hint, orders of magnitude cheaper)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I can go into more detail on any of these parts. I'm also interested in comparable services I missed. Get in touch!&lt;/p&gt;

</description>
      <category>thumbrella</category>
      <category>saas</category>
    </item>
    <item>
      <title>Thumbrella Developer Intro</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Mon, 21 Sep 2026 07:15:18 +0000</pubDate>
      <link>https://dev.to/shredwheat/thumbrella-developer-intro-4gm4</link>
      <guid>https://dev.to/shredwheat/thumbrella-developer-intro-4gm4</guid>
      <description>&lt;p&gt;I took a pass at assembling a video intro for Thumbrella. This 4 minute overview should get you hyped for a seemingly simple commodity feature. Making this work well has turned into a deep and captivating project for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/HVQ6wjGKZCc" rel="noopener noreferrer"&gt;&lt;strong&gt;Youtube - Thumbrella Quick Developer Intro&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I discovered I may need need a few more videos to dig into the deeper details.&lt;/p&gt;

&lt;p&gt;Highlighted in the video is the custom &lt;code&gt;&amp;lt;tbr-thumb&amp;gt;&lt;/code&gt; html component. I'm amazed that browsers pack so much functionality into simple looking dom elements like this. There's a &lt;a href="https://thumbrella.dev/docs/components/" rel="noopener noreferrer"&gt;full documentation section&lt;/a&gt; about this on the website.&lt;/p&gt;

</description>
      <category>thumbnail</category>
      <category>server</category>
      <category>introduction</category>
      <category>thumbrella</category>
    </item>
    <item>
      <title>HTTP Caching Intro</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Wed, 16 Sep 2026 14:00:58 +0000</pubDate>
      <link>https://dev.to/shredwheat/http-caching-intro-654</link>
      <guid>https://dev.to/shredwheat/http-caching-intro-654</guid>
      <description>&lt;p&gt;This article is a light introduction to how HTTP servers and clients cache data to improve performance. Taking advantage of this lets clients perform well despite network overhead. This was all information I got to learn while implementing my own open source thumbnail server, &lt;a href="https://Thumbrella.dev" rel="noopener noreferrer"&gt;Thumbrella&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;HTTP caching is opt-in behavior for servers and clients, so even partial implementations can start seeing immediate benefits. The full caching specification gets fairly extensive, growing in complexity once proxies and failures are involved. Here are a couple resources describing the full details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching" rel="noopener noreferrer"&gt;MDN: HTTP Caching Guide&lt;/a&gt;, conceptual overview and diagrams of the flow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://httpwg.org/specs/rfc9111.html" rel="noopener noreferrer"&gt;RFC 9111&lt;/a&gt;, the actual spec for all the details and edge-case behaviors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;p&gt;There are two concepts for caching HTTP requests. Most cachable content will have an expiration. Before expiring the contents are considered &lt;strong&gt;fresh&lt;/strong&gt;, and can be reused immediately, with no additional checks.&lt;/p&gt;

&lt;p&gt;After the cached content is expired it is no longer considered fresh. A followup request should be &lt;strong&gt;validated&lt;/strong&gt; by the server. This allows the server to avoid transferring identical information when the client and server agree the existing cached contents are up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP Headers
&lt;/h2&gt;

&lt;p&gt;Cache management is handled by various HTTP headers in each request and response. The initial response will return any combination of these. The client will return some of these back to the server, and follow the reported guidelines for the others.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
  &lt;th&gt;Initial Response Header&lt;/th&gt;
  &lt;th&gt;Followup Request Header&lt;/th&gt;
  &lt;th&gt;Information&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
  &lt;td&gt;8 different directives (comma separated) that control caching behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/td&gt;
  &lt;td&gt;&lt;code&gt;If-None-Match&lt;/code&gt;&lt;/td&gt;
  &lt;td rowspan="2"&gt;Server validates if cache data is current by returning status &lt;code&gt;304 Not Modified&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/td&gt;
  &lt;td&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;code&gt;Vary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
  &lt;td&gt;A list of request header names that modify the content, like `Accepts`.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;code&gt;Expires&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
  &lt;td&gt;A superseded way to define &lt;code&gt;max-age&lt;/code&gt;, now defined by the &lt;code&gt;Cache-Control&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One extra note on headers. Requests that provide an &lt;code&gt;Authorization&lt;/code&gt; header are generally not expected to be cached. Or when cached, privately stored for whatever credentials were provided.&lt;/p&gt;

&lt;h2&gt;
  
  
  ETag and Last-Modified
&lt;/h2&gt;

&lt;p&gt;Let’s start with the two conditional validation headers, since they are conceptually simplest.&lt;/p&gt;

&lt;p&gt;These are two values the server can provide with any response. The &lt;code&gt;ETag&lt;/code&gt; header represents an arbitrary hashing or opaque value generated by the server. The &lt;code&gt;Last-Modified&lt;/code&gt; can be an HTTP-date timestamp representing when the content was last updated. Neither of these values needs to be based on the returned body. Servers generating dynamic content will generate these values based on the inputs.&lt;/p&gt;

&lt;p&gt;When making a followup request the client can return these same values in different headers named &lt;code&gt;If-None-Match&lt;/code&gt;  (for the &lt;code&gt;ETag&lt;/code&gt; value) and &lt;code&gt;If-Modified-Since&lt;/code&gt; (for the &lt;code&gt;Last-Modified&lt;/code&gt; value). When provided the server is allowed to return an empty response with the status &lt;code&gt;304 Not Modified&lt;/code&gt;. This allows the client to know its existing cached value for this request is still up to date, without transferring the content a second time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache-Control
&lt;/h2&gt;

&lt;p&gt;This header defines a combination of 8 different directives, separated by commas. These can be loosely organized into two categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freshness
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max-age=&amp;lt;seconds&amp;gt;&lt;/code&gt;
This is the most common directive for controlling client side freshness. This tells a browser it can reuse the contents within the given number of seconds. This is the only directive that has a value. There is also a &lt;code&gt;s-maxage&lt;/code&gt; alternative that is ignored by browsers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;immutable&lt;/code&gt;
Means the contents are never expected to change. The cached results will can be reused for as long as the client wants to store them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no-cache&lt;/code&gt;
The contents are cached, but never considered fresh. The client should always followup with one of the &lt;code&gt;if&lt;/code&gt; headers to validate the contents are still valid. This is a slightly misleading name, but is defined to mean “never fresh”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Behavior
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;no-store&lt;/code&gt;
Disable all caching. Used for sensitive responses like tokens and personal data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public&lt;/code&gt;
Response can be stored in shared caches, even when &lt;code&gt;Authorization&lt;/code&gt; headers are provided.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;private&lt;/code&gt;
Only the end-user's browser cache should store this, not proxies or intermediate results.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;must-revalidate&lt;/code&gt;
This is a stricter &lt;code&gt;no-cache&lt;/code&gt; that also means the cached values should not be used if the server cannot be reached to revalidate its contents.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thumbrella
&lt;/h2&gt;

&lt;p&gt;All this was crucial to learn and implement for my online thumbnailing server, &lt;a href="https://thumbrella.dev" rel="noopener noreferrer"&gt;Thumbrella&lt;/a&gt;. This implements intelligent caching is for all media requests, and a critical part of generating fast and interactive results.&lt;/p&gt;

&lt;p&gt;Much of this had to be encapsulated for thumbnail clients because the API is built around handling bulk batch requests, returning multiple thumbnail results in a single request. (Some results potentially cached and some not cached). Thumbrella cleverly simplifies all this, although unnecessary to learn if using any of the &lt;a href="https://thumbrella.dev/docs/client/" rel="noopener noreferrer"&gt;high level client libraries&lt;/a&gt; through Javascript, Python, or Rust.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thumbrella.dev/docs/server/" rel="noopener noreferrer"&gt;Self hosting&lt;/a&gt; Thumbrella is an easy way to provide thumbnails for your own media libraries, supporting over 100 formats.&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%2Fxekfditqd7v6svccu5yv.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%2Fxekfditqd7v6svccu5yv.png" alt="Thumbrella Logo" width="224" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>http</category>
      <category>introduction</category>
      <category>cache</category>
      <category>thumbrella</category>
    </item>
    <item>
      <title>winget package experience</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Wed, 26 Aug 2026 06:39:50 +0000</pubDate>
      <link>https://dev.to/shredwheat/winget-package-experience-27la</link>
      <guid>https://dev.to/shredwheat/winget-package-experience-27la</guid>
      <description>&lt;p&gt;As a user I've enjoyed using &lt;a href="https://learn.microsoft.com/en-us/windows/package-manager/winget/" rel="noopener noreferrer"&gt;winget&lt;/a&gt; to fetch and manage command line utilities on Windows. So when looking at options for my own server project I was interested in trying this deployment option.&lt;/p&gt;

&lt;p&gt;One thing I'm I appreciate is the ability for a project Manifest to define a package from a standalone executable, or a zip file with the executable included. No mucking about with installer executables or msi as a maintainer.&lt;/p&gt;

&lt;p&gt;The way &lt;a href="https://github.com/microsoft/winget-pkgs" rel="noopener noreferrer"&gt;winget packages&lt;/a&gt; are managed reminds me of how FreeBSD manages all their package recipes in a monolithic source control repository named &lt;a href="https://en.wikipedia.org/wiki/FreeBSD_Ports" rel="noopener noreferrer"&gt;ports&lt;/a&gt;. For winget this is done as a Git repository of an absolutely massive, overwhelming scale.&lt;/p&gt;

&lt;p&gt;The first rule is, you don't check out this Git repository. You'll need to deal with sparse checkouts and never focus your eyes too closely at the results. So far I've had good luck cloning my fork with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1  &lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;blob:none &lt;span class="nt"&gt;--sparse&lt;/span&gt; https://github.com/my_fork_owner/winget-pkgs 
git sparse-checkout &lt;span class="nb"&gt;set &lt;/span&gt;manifests/p/ProjectStartingWith_P
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there I can copy the manifest files into the proper versioned subdirectory. Then &lt;code&gt;git add&lt;/code&gt; and &lt;code&gt;git commit&lt;/code&gt; and &lt;code&gt;git push&lt;/code&gt; like regular.&lt;/p&gt;

&lt;p&gt;But then the "real work" begins; a pull request of your fork back to the main repo. Maintaining such a thing is somewhat unthinkable. What army of volunteers or workers could ever manage such a thing?&lt;/p&gt;

&lt;p&gt;The repository is maintained by a fleet of Github Actions that manage CLAs, validate submissions, run virus scanners, and a deep array of byzantine processes. These manage a collection of labels on the pull request. Things like &lt;code&gt;New-Package&lt;/code&gt;, &lt;code&gt;Needs-Attention&lt;/code&gt;, and the dreaded &lt;code&gt;Validation-Defender-Error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Maintainers then triage the pull requests based on these labels. Generally using predefined response templates Github provides.&lt;/p&gt;

&lt;p&gt;In my case my package is fairly stuck. It seems Rust compiled binaries have a tendency to trigger the Defender virus checks. What makes this annoying is that a local Defender scan finds no problem with my binary. I also submitted a false positive check to the MSI team and the results also came back clean. &lt;/p&gt;

&lt;p&gt;I'm somewhat stuck waiting for the winget process to potentially re-evaluate my binary for release (or grant some type of exception?).&lt;/p&gt;

&lt;p&gt;The massive scale of it all is both impressive and a bit intimidating. Also a bit frustrating as my problems seem lost in an ocean of other projects and packages all vying for similar attentions.&lt;/p&gt;

&lt;p&gt;In the meantime, keep rooting for &lt;a href="https://github.com/microsoft/winget-pkgs/pull/422040" rel="noopener noreferrer"&gt;Pull Request #422040&lt;/a&gt; finding a road to release.&lt;/p&gt;

</description>
      <category>winget</category>
    </item>
    <item>
      <title>Comp Language Tags</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Fri, 03 Apr 2026 04:17:35 +0000</pubDate>
      <link>https://dev.to/shredwheat/comp-language-tags-bga</link>
      <guid>https://dev.to/shredwheat/comp-language-tags-bga</guid>
      <description>&lt;p&gt;Comp allows defining a hierarchy of named values that can be used anywhere in the namespace. These are called tags and have several useful properties.&lt;/p&gt;

&lt;p&gt;Comp uses tags almost like keywords throughout the language. The builtin boolean types &lt;code&gt;true&lt;/code&gt; are and &lt;code&gt;false&lt;/code&gt; are just regular tags. Even flow control statements like &lt;code&gt;skip&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; (known as &lt;code&gt;continue&lt;/code&gt; and &lt;code&gt;break&lt;/code&gt; in many other languages) are just tags.&lt;/p&gt;

&lt;p&gt;Tags are defined in a module as a simple hierarchy. Hierarchies from other modules can be extended within an internal module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;medium&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="n"&gt;maybe&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comp namespaces work in that any definition can be referenced by its fully qualified name or as just the leaf portion of the name that is non ambiguous. So &lt;code&gt;maybe&lt;/code&gt; works just as well as &lt;code&gt;bool.maybe&lt;/code&gt; inside of our namespace.&lt;/p&gt;

&lt;p&gt;In this case we've extended bool with our own crazy value. But the original module has no knowledge of our extensions. This allows us to extend but not pollute tag hierarchies.&lt;/p&gt;

&lt;p&gt;There's a mountain of other details. Tags are the quick and easy way to avoid strings to define types or behaviors. A list of other places tags have an influence on the language.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tags are used to define units, which we've already seen in values like &lt;code&gt;24#second&lt;/code&gt; or &lt;code&gt;"select verion()"#sql&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tags have a high precedence when morphing structs to shapes, something to demonstrate in a future post.

&lt;ul&gt;
&lt;li&gt;This has a profoundly useful impact on assigning parameters to function calls.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tags are both shapes and singleton values, which gives them a dual role.&lt;/li&gt;
&lt;li&gt;Tags from unknown modules can be serialized and used as a "raw tag", which can be promote later given a module that can define them. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>comp</category>
    </item>
    <item>
      <title>Comp Language Numbers</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Thu, 02 Apr 2026 04:56:31 +0000</pubDate>
      <link>https://dev.to/shredwheat/comp-language-numbers-2d8l</link>
      <guid>https://dev.to/shredwheat/comp-language-numbers-2d8l</guid>
      <description>&lt;p&gt;Comp's number values are not restricted to traditional hardware representations and their shortcomings. Comp numbers have huge, open ended precision with mathematical operations that do not accumulate errors. A single &lt;code&gt;~num&lt;/code&gt; type that represents all possible numbers.&lt;/p&gt;

&lt;p&gt;This is somewhat of a game changer for writing code. It feels surprisingly futuristic to do things like convert between Farenheit to Celsius then back and end up with the exact same number you started with. No "approximate" results here. &lt;/p&gt;

&lt;p&gt;It should make you wonder why anyone must tolerate &lt;code&gt;(1/3)*3 != 1.0&lt;/code&gt; or rounding &lt;code&gt;256.49999&lt;/code&gt; to &lt;code&gt;257&lt;/code&gt; in the year 2026? There is an excellent reason to tolerate these inaccuracies; Computers are astonishingly fast and efficient at storing and computing numbers. But I get the sense mostly it's because of decades of tolerating and being forced to accept. &lt;/p&gt;

&lt;p&gt;When those come into play the language will use allocated of arrays of numbers; much like the amazing Numpy which makes storage and computation of numbers more tasteful in Python.&lt;/p&gt;

&lt;p&gt;When interacting with legacy languages it is still useful to define numbers in terms they can understand. This is one area where Comp's limits shine brightly. I'll cover limits more in a future post, but the quick definition is that limits can be placed on defined types. A quick shape definition creates a type that can't overflow or allow lossy conversion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="n"&gt;uint8&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;integer&lt;/span&gt; &lt;span class="n"&gt;ge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;le&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll get deeper into shape definitions later. The language does provide functions that handle lossy conversions in specific ways, and it's easy to write your own.&lt;/p&gt;

&lt;p&gt;Comp numbers are not allowed to be illegal values (&lt;code&gt;inf&lt;/code&gt; or &lt;code&gt;nan&lt;/code&gt;) by default. But those can be allowed with union types where desirable.&lt;/p&gt;

&lt;p&gt;I highly recommend Julia Evan's article that explains the how and whys of problems that will never be a problem for Comp developers.&lt;br&gt;
&lt;a href="https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/" rel="noopener noreferrer"&gt;https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>coding</category>
      <category>computerscience</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Comp Language Struct</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Wed, 01 Apr 2026 05:59:25 +0000</pubDate>
      <link>https://dev.to/shredwheat/comp-language-struct-5j6</link>
      <guid>https://dev.to/shredwheat/comp-language-struct-5j6</guid>
      <description>&lt;p&gt;An ongoing series of my thoughts and ideas for my Comp programming language.&lt;/p&gt;

&lt;p&gt;Comp provides a single container type. It's a flexible container that combines both ordered and named fields in a unique style. A struct literal is defined by a series of fields in side of curly braces. Named and unnamed fields can be mixed freely, while always remaining ordered. Field values can be mixed and matched with any types. All data in Comp is immutable, including these structures. &lt;/p&gt;

&lt;p&gt;One way to think of this is similar to how Python arguments are defined for a function. Some arguments are positional and some arguments are named. When combined with Comp's shape values we get defaults and flexible morphing (casts). This makes Comp's structs work like Python arguments, but you don't need to invoke a function to assemble them.&lt;/p&gt;

&lt;p&gt;Some examples likely make this clearer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"cat"&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Joe"&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"it did not work"&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"car"&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"bike"&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2025&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;Remember, Comp's syntax uses no separators and whitespace is arbitrary. These statements could be split over multiple lines, joined, or justified in whatever style felt appropriate.&lt;/p&gt;

&lt;p&gt;Fields are accessed either with dotted field name or positionally using a special index field. All fields can be referenced by index, which works for both named and unnamed fields. And the first index starts at 0, this does not allow "negative indexing" from the back of the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;vehicles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical field names are simple text tokens, which support &lt;code&gt;kebab-case&lt;/code&gt;. But any value can be used as a field name, including numbers, tags, and other structures. These "value" field named are wrapped in single quotes and allow any value or expression. Double quotes can be used as text when the field name isn't a valid token. These same rules are used for defining fields and referencing fields. There are few other "advanced field lookup" syntaxes but these ones go a long way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;  &lt;span class="s"&gt;"Server Port"&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s"&gt;"Server Port"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>comp</category>
    </item>
    <item>
      <title>Comp Language Syntax</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Tue, 31 Mar 2026 04:26:55 +0000</pubDate>
      <link>https://dev.to/shredwheat/comp-language-syntax-1ca0</link>
      <guid>https://dev.to/shredwheat/comp-language-syntax-1ca0</guid>
      <description>&lt;p&gt;An ongoing series in my quest to untangle my own thoughts and goals for the Comp programming language.&lt;/p&gt;

&lt;p&gt;A language focused on developer experience? So much of my focus has been on the syntax and grammar. The entire concept is built on a handful of core fundamental rules that everything expands from.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No separators or terminators&lt;/li&gt;
&lt;li&gt;Whitespace independent

&lt;ul&gt;
&lt;li&gt;No line-based statements&lt;/li&gt;
&lt;li&gt;No indentation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along with these fundamentals there are a few other strongly motivated designs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;kabab-case&lt;/code&gt; for identifiers&lt;/li&gt;
&lt;li&gt;No keywords&lt;/li&gt;
&lt;li&gt;Avoid nesting braces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along with that there is has been from-the-start requirement that the structure literal has two forms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{1 2 3}&lt;/code&gt; for positional sequences (lists, arrays)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{a=1 b=2}&lt;/code&gt; for named mappings (dicts, hashes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This has been a long and ongoing puzzle, but the payoff has been spectacular. Let's look at another small code example and call out a few highlights.&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="o"&gt;!&lt;/span&gt;func shopping-cart-demo &lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Apple"&lt;/span&gt; &lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.00&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Banana"&lt;/span&gt; &lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.50 &lt;span class="nv"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Apple"&lt;/span&gt; 1.00 &lt;span class="nv"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;  // replace previous apples
  &lt;span class="o"&gt;}&lt;/span&gt;
  | reduce :&lt;span class="o"&gt;(&lt;/span&gt;| upsert :&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$.&lt;/span&gt;name&lt;span class="o"&gt;))&lt;/span&gt;
  | cart-total
  | output &lt;span class="s2"&gt;"Total: %()"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Keywords use a &lt;code&gt;!&lt;/code&gt; prefix. This makes them a bit noisier than I'd like, but it's not a bad design to make them pronounced. This also allows freely introducing new keywords in the future.&lt;/li&gt;
&lt;li&gt;Whitespace independence means this entire definition could be placed on a single line, or each statement split or joined as the author decides is most readable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a history of experiments, compromises, and undesirable workarounds to assemble these pieces. Much of this time I wasn't sure these constraints could even be appeased.&lt;/p&gt;

&lt;p&gt;I've been using the &lt;a href="https://github.com/lark-parser/lark" rel="noopener noreferrer"&gt;Lark&lt;/a&gt; library to handle parsing. I'd never experimented with it before, but now have a good deal of experience stretching it's LALR rules. This gives an ideal O(n) performance. I have been surprised at the cost of building the grammar at runtime. Fortunately the library is quite prepared for this and comes with some high level caching options.&lt;/p&gt;

</description>
      <category>comp</category>
    </item>
    <item>
      <title>Comp the Language</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Sun, 29 Mar 2026 22:42:38 +0000</pubDate>
      <link>https://dev.to/shredwheat/comp-the-language-1ifd</link>
      <guid>https://dev.to/shredwheat/comp-the-language-1ifd</guid>
      <description>&lt;p&gt;I've been brewing on ideas for a new programming language for months now. That's nothing anybody should need to notice, I see three new language announcements a day on various Reddit channels.&lt;/p&gt;

&lt;p&gt;Perhaps tinkering with a new language is folly; a new programming language in 2026? The dawn of AI coding is eminent, so who is looking at programming languages?&lt;/p&gt;

&lt;p&gt;Well, I do it because it captivates me. It is my itch these days, and therefore, I scratch. Perhaps I will soon learn the reasons why more languages isn't best languages. Perhaps this will become the new Coffeescript for Python. Perhaps this will become the new Python itself. In reality, no. This is a project by myself for myself.&lt;/p&gt;

&lt;p&gt;So why even share? Posting and describing the language should help my organize thoughts and ideas. There's nothing like having to explain a process to shine a flashlight on the weakest links, and potentially the greatest links.&lt;/p&gt;

&lt;p&gt;It's also folly to be thinking a new programming language will discover anything new or revolutionary. There are hundreds of amazing languages and libraries created by smarter people than myself. At some point additional perspectives will help, if not for the potential language, at least for my own expectations.&lt;/p&gt;

&lt;p&gt;Project links? Example code? That must wait. For now I'll leave the most minimal of examples behind. Details must wait for future posts.&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="o"&gt;!&lt;/span&gt;func greetings ~nil &lt;span class="o"&gt;(&lt;/span&gt;
   &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"USERNAME"&lt;/span&gt; &lt;span class="s2"&gt;"USER"&lt;/span&gt; &lt;span class="s2"&gt;"LOGNAME"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
   | map :&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;| get-env |? skip&lt;span class="o"&gt;)&lt;/span&gt;
   | first &lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"World"&lt;/span&gt;
   | output &lt;span class="s2"&gt;"Hello, %()!"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>comp</category>
    </item>
    <item>
      <title>Monkey AIslands - Part 1</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Tue, 03 Feb 2026 05:41:31 +0000</pubDate>
      <link>https://dev.to/shredwheat/monkey-aislands-part-1-46fm</link>
      <guid>https://dev.to/shredwheat/monkey-aislands-part-1-46fm</guid>
      <description>&lt;p&gt;In the legendary &lt;a href="https://en.wikipedia.org/wiki/The_Secret_of_Monkey_Island" rel="noopener noreferrer"&gt;Secret of Monkey Island&lt;/a&gt;, one of the puzzles is to assemble an arsenal of witty insults and devastating comebacks to use during sword fights. Some say the wit is mightier than the sword.&lt;/p&gt;

&lt;p&gt;I've decided to see how well various modern LLMs perform at matching the appropriate comebacks to these classic adventure game challenges.&lt;/p&gt;

&lt;p&gt;A fully accomplished sword fighter will know 16 different insults and their responses. To add difficulty to this comparison I've added 4 generic comebacks that do not pair to any insult.&lt;/p&gt;

&lt;p&gt;If this pinnacle of computerized entertainment predates your lifespan by decades, you can still find reasonably recent versions on &lt;a href="https://store.steampowered.com/app/32360/The_Secret_of_Monkey_Island_Special_Edition/" rel="noopener noreferrer"&gt;Steam&lt;/a&gt;, &lt;a href="https://www.gog.com/en/game/the_secret_of_monkey_island_special_edition" rel="noopener noreferrer"&gt;GoG&lt;/a&gt;, and likely other digital retailers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Prompt
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Given this list of 16 insults and 20 witty insults, match the best response to each challenge. Notice there are several generic responses that will be unused in the final answers. List orders are randomized. These are from a classic, light-hearted video game related to pirates and sword fighting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In hindsight, I see I added unintential challenge by mis-wording my prompt. Carry on LLMs!&lt;/p&gt;




&lt;h2&gt;
  
  
  The Insults
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[spoilers]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Soon you'll be wearing my sword like a shish kebab!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;First you'd better stop waving it like a feather-duster.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are no words for how disgusting you are.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Yes, there are. You just never learned them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My handkerchief will wipe up your blood!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;So you got that job as janitor, after all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You fight like a dairy farmer.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;How appropriate. You fight like a cow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is the END for you, you gutter-crawling cur!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;And I've got a little TIP for you, get the POINT?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nobody's ever drawn blood from me and nobody ever will.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You run THAT fast?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;People fall at my feet when they see me coming.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Even BEFORE they smell your breath?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I've heard you were a contemptible sneak.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Too bad no one's ever heard of YOU at all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're no match for my brains, you poor fool.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I'd be in real trouble if you ever used them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You make me want to puke.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;You make me think somebody already did.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have you stopped wearing diapers yet?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Why, did you want to borrow one?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have the manners of a beggar.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I wanted to make sure you'd feel comfortable with me.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I'm not going to take your insolence sitting down!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Your hemorrhoids are flaring up again, eh?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I got this scar on my face during a mighty struggle!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I hope now you've learned to stop picking your nose.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I've spoken with apes more polite then you.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I'm glad to hear you attended your family reunion.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I once owned a dog that was smarter then you.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;He must have taught you everything you know.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The unused responses that I added are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then get ready to feel the thunder.&lt;/li&gt;
&lt;li&gt;I've already taken liberties with your mother.&lt;/li&gt;
&lt;li&gt;Not before you FACE my wrath.&lt;/li&gt;
&lt;li&gt;Fortunately, I've been preparing my whole life.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Responses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GPT-4.1
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Matching Score 14/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are the classic insult-comeback pairs from the pirate sword-fighting game "The Secret of Monkey Island." Each insult is matched with its best witty response. Some comebacks are generic and may fit more than one insult, but these are the canonical matches from the game. Let me know if you want the pairs in a different format!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Correctly identified these were from Secret of Monkey Island, then proceeded to get a few wrong. It reused some of the responses multiple times.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Missed responses&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Soon you'll be wearing my sword like a shish kebab!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;How appropriate. You fight like a cow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are no words for how disgusting you are.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I'm glad to hear you attended your family reunion.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GPT-5.1
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Minimal and correct response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are the best matches:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  GPT-5.2
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Least helpful response of generating a table containing numbers to answer numbers. It really put a lot of work into these answers, definitely on the slower of the group. No summary or response other than the the resulting answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Haiku 4.5
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 0/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I appreciate the message, but I need to let you know that this request isn't related to programming. The insult-and-comeback matching task appears to be from "The Secret of Monkey Island," a classic adventure game. While it's a fun puzzle, it falls outside what I'm designed to help with as your AI programming assistant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What good can my coding assistant be if it can't match rapier wits with the code I may come across?&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Sonnet 4.5
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is from "The Secret of Monkey Island"! Here are the matched insult-comeback pairs:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aha, this was one of the few (only?) models to choose one of my generic zinger answers. It managed to miss a few others on its own. But I do appreciate it's enthusiasm for recognizing the source of the material.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Missed responses&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Soon you'll be wearing my sword like a shish kebab!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;And I've got a little TIP for you, get the POINT?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is the END for you, you gutter-crawling cur!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Not before you FACE my wrath.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Claude Opus 4.5
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Looking at these classic Monkey Island insult sword fighting exchanges, here are the matched pairs:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quick and accurate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grok Code Fast
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 11/16&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a strong showing, this really does fight like a dairy farmer. No additional notes or output, just the straight answers.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Missed Responses&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My handkerchief will wipe up your blood!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Why, did you want to borrow one?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is the END for you, you gutter-crawling cur!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Not before you FACE my wrath.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have you stopped wearing diapers yet?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I hope now you've learned to stop picking your nose.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have the manners of a beggar.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;So you got that job as janitor, after all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I got this scar on my face during a mighty struggle!&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;And I've got a little TIP for you, get the POINT?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini 2.5 Pro
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are the correct pairings for the insults and comebacks:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All exactly correct answers with a minimal commentary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini 3 Flash
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are the matched insults and comebacks from the classic Monkey Island series:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the only model that showed the 4 unused answers after showing its choices for each insult.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini 3 Pro
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Score 16/16&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are the correct matches for the Insult Sword Fighting exchanges from The Secret of Monkey Island:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes. All the Gemini models I tested performed excellently.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Summary
&lt;/h2&gt;

&lt;p&gt;Many of the models perfectly matched the insults and most correctly identified this material comes from the game, The Secret of Monkey Island. I guess the tie breaker then comes down to the summary, commentary, and formatting generated by the LLM.&lt;/p&gt;

&lt;p&gt;Part of me wonders how much "thought" went into these answers, versus potentially being baked into the general body of knowledge of each model?&lt;/p&gt;

&lt;h3&gt;
  
  
  Best
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Gemini 3 Flash&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While several got perfect answers and formatted the results nicely, this wins by using the "least tokens multiplier for me in VSCode. I also felt it provided the most comprehensive answer. Well done Gemini.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worst
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Grok Code Fast&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One could argue this did better than Haiku, but in this case I'd rather be called crazy than lied to.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Colors with Rio's oklab color space</title>
      <dc:creator>Peter Shinners</dc:creator>
      <pubDate>Mon, 03 Feb 2025 05:59:31 +0000</pubDate>
      <link>https://dev.to/shredwheat/colors-with-rios-oklab-color-space-52fb</link>
      <guid>https://dev.to/shredwheat/colors-with-rios-oklab-color-space-52fb</guid>
      <description>&lt;p&gt;While browsing the Rio source I noticed it &lt;a href="https://github.com/rio-labs/rio/blob/main/rio/color.py" rel="noopener noreferrer"&gt;manages colors &lt;/a&gt; in the &lt;a href="https://bottosson.github.io/posts/oklab/" rel="noopener noreferrer"&gt;Oklab color space&lt;/a&gt; which I hadn't heard of. I also wasn't expecting the technique it used in the &lt;code&gt;Color.brighter&lt;/code&gt; and &lt;code&gt;Color.darker&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;I've &lt;a href="https://dev.to/shredwheat/minimal-rio-intro-1hpm"&gt;been impressed with Rio in the past &lt;/a&gt; for allowing minimal Python code to create user interfaces. Curious with how these color operations felt in practice, I created a &lt;a href="https://github.com/PeterShinners/rioquicklooks/blob/colors/colors.py" rel="noopener noreferrer"&gt;quick 50 line demo&lt;/a&gt; to interact with a simple color wheel.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1052906026" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It looks like the Oklab color space excels at color gradients by giving a natural range through each hue. Unfortunately I don't immediately like how Rio is adjusting brightness with these methods. In the video, see how the outer light colors change through their range of brightness separately from the inner dark colors. This means &lt;code&gt;Color.lighter(0.1)&lt;/code&gt; will have a drastic affect on dark colors and minimal change to lighter colors. This could still be a desirable quality, so I'm unwilling declare this as good or bad. &lt;/p&gt;

&lt;p&gt;Separately; points to Rio once again for making small and lightweight tools fun to develop. The community has been lightning quick on my wild questions and ideas. Also points to Oklab for their impressive gradient examples.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rio</category>
      <category>color</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
