<?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: Cesar Gimenes</title>
    <description>The latest articles on DEV Community by Cesar Gimenes (@crgimenes).</description>
    <link>https://dev.to/crgimenes</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%2F254474%2F8bf24d58-8608-442b-b76e-a4996c372d8c.png</url>
      <title>DEV Community: Cesar Gimenes</title>
      <link>https://dev.to/crgimenes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/crgimenes"/>
    <language>en</language>
    <item>
      <title>I built a tiny wind tunnel in Go</title>
      <dc:creator>Cesar Gimenes</dc:creator>
      <pubDate>Wed, 08 Jul 2026 05:09:01 +0000</pubDate>
      <link>https://dev.to/crgimenes/i-built-a-tiny-wind-tunnel-in-go-5b5c</link>
      <guid>https://dev.to/crgimenes/i-built-a-tiny-wind-tunnel-in-go-5b5c</guid>
      <description>&lt;p&gt;I built &lt;strong&gt;Kutta&lt;/strong&gt;, a small 2D wind tunnel for aeromodelers, game developers, and anyone who enjoys watching air misbehave.&lt;/p&gt;

&lt;p&gt;It is written in Go, uses Ebitengine for rendering, and runs as a single desktop application.&lt;/p&gt;

&lt;p&gt;The project is open source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crgimenes/kutta" rel="noopener noreferrer"&gt;https://github.com/crgimenes/kutta&lt;/a&gt;&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%2F99cmq0lifjfzilxhkq2q.gif" 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%2F99cmq0lifjfzilxhkq2q.gif" alt="Kutta running" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Kutta started as a simple visual experiment: stream air past an airfoil and make the flow visible.&lt;/p&gt;

&lt;p&gt;Not as a replacement for real CFD software. Not as an engineering tool for designing aircraft. The goal is more modest and, in my opinion, more useful for experimentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give immediate visual intuition about what happens around an airfoil.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can change the angle of attack and watch the flow field react. You can see the wake. You can toggle different visualization modes. You can draw your own shapes. You can split a wing into a wing and a flap, animate the control surface, and see the wake become unstable when the geometry or angle gets too aggressive.&lt;/p&gt;

&lt;p&gt;It is a toy in the best possible sense: simple enough to play with, but grounded enough to teach something.&lt;/p&gt;

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

&lt;p&gt;Kutta visualizes several aspects of the flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speed field;&lt;/li&gt;
&lt;li&gt;vorticity;&lt;/li&gt;
&lt;li&gt;pressure-like scalar field;&lt;/li&gt;
&lt;li&gt;smoke streaklines;&lt;/li&gt;
&lt;li&gt;lift and drag vectors;&lt;/li&gt;
&lt;li&gt;wake behavior;&lt;/li&gt;
&lt;li&gt;separation when the angle of attack becomes too high.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also includes NACA 4-digit and 5-digit airfoil generation, so you can type a profile such as &lt;code&gt;2412&lt;/code&gt;, &lt;code&gt;0012&lt;/code&gt;, or &lt;code&gt;23012&lt;/code&gt; and get the corresponding shape without loading anything from disk.&lt;/p&gt;

&lt;p&gt;And yes, there is also a &lt;code&gt;neko&lt;/code&gt; airfoil.&lt;/p&gt;

&lt;p&gt;It is terrible.&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qualitative, not validated CFD
&lt;/h2&gt;

&lt;p&gt;This part is important.&lt;/p&gt;

&lt;p&gt;Kutta is &lt;strong&gt;not&lt;/strong&gt; a validated CFD tool.&lt;/p&gt;

&lt;p&gt;It uses lattice units instead of physical units. It can show useful flow behavior such as stagnation, wake formation, suction over the upper side of the airfoil, and separation as the angle grows, but the numeric values should not be treated as engineering data.&lt;/p&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;intuition;&lt;/li&gt;
&lt;li&gt;teaching;&lt;/li&gt;
&lt;li&gt;visual experiments;&lt;/li&gt;
&lt;li&gt;aeromodeling curiosity;&lt;/li&gt;
&lt;li&gt;game/tool inspiration;&lt;/li&gt;
&lt;li&gt;pretty projector demos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use it to size a real aircraft wing.&lt;/p&gt;

&lt;p&gt;That boundary is intentional. I prefer tools that are honest about what they are.&lt;/p&gt;

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

&lt;p&gt;The solver uses a 2D Lattice-Boltzmann method: D2Q9 with BGK collision.&lt;/p&gt;

&lt;p&gt;The channel has a free stream coming from the left. The body is represented as a no-slip wall using half-way bounce-back. Forces are estimated from pressure integration over the body faces.&lt;/p&gt;

&lt;p&gt;That means Kutta can capture form drag and lift behavior, but it does not model everything. For example, skin friction is ignored, so drag is understated. That is a known limitation, not a hidden bug.&lt;/p&gt;

&lt;p&gt;The project is split into a few internal packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;lbm&lt;/code&gt;: the flow solver;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;foil&lt;/code&gt;: NACA airfoil generation;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scene&lt;/code&gt; and &lt;code&gt;sceneio&lt;/code&gt;: editable multi-object scenes;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;viz&lt;/code&gt;: color maps and smoke tracer particles;&lt;/li&gt;
&lt;li&gt;the app layer: Ebitengine loop, editor, input, menus, and rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lower-level packages do not depend on rendering and can be tested headlessly. There is also a snapshot tool that renders fields to PNG, which is useful for sanity-checking behavior without opening a window.&lt;/p&gt;

&lt;h2&gt;
  
  
  The editor
&lt;/h2&gt;

&lt;p&gt;Kutta includes a simple shape editor.&lt;/p&gt;

&lt;p&gt;You can draw a closed shape, move vertices, and use Bézier handles to curve edges. You can also cut and rejoin shapes, which makes it possible to split an airfoil into separate parts.&lt;/p&gt;

&lt;p&gt;That is how a wing can become a wing plus flap.&lt;/p&gt;

&lt;p&gt;There is also a basic animation mode. You can scrub a timeline, add keyframes, and animate the pose of each part while the flow keeps running.&lt;/p&gt;

&lt;p&gt;Scenes are saved as &lt;code&gt;.afoil&lt;/code&gt; files. The format is small, textual, and based on s-expressions.&lt;/p&gt;

&lt;p&gt;I like this kind of format because it is easy to inspect, diff, and keep in Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Go?
&lt;/h2&gt;

&lt;p&gt;Go is not the first language people usually associate with simulations or visual creative tools.&lt;/p&gt;

&lt;p&gt;That is one reason I like using it for this type of project.&lt;/p&gt;

&lt;p&gt;For Kutta, Go gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast compilation;&lt;/li&gt;
&lt;li&gt;simple cross-platform builds;&lt;/li&gt;
&lt;li&gt;good standard tooling;&lt;/li&gt;
&lt;li&gt;readable code;&lt;/li&gt;
&lt;li&gt;easy packaging;&lt;/li&gt;
&lt;li&gt;enough performance for this kind of real-time qualitative simulation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ebitengine handles the rendering and application loop. The result is a normal desktop app, not a large runtime environment or a complex toolchain.&lt;/p&gt;

&lt;p&gt;For users, that means: download one executable and run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why make it visual?
&lt;/h2&gt;

&lt;p&gt;The original motivation was not only technical.&lt;/p&gt;

&lt;p&gt;I wanted something that felt alive.&lt;/p&gt;

&lt;p&gt;A static chart can explain lift. A formula can describe the idea. A textbook diagram can show separation. But watching a wake form behind a wing while you change the angle of attack gives a different kind of understanding.&lt;/p&gt;

&lt;p&gt;It is less precise, but more immediate.&lt;/p&gt;

&lt;p&gt;That matters for learning.&lt;/p&gt;

&lt;p&gt;It also matters for open source visibility. A short video of Kutta running reached far more people than a traditional technical post would have. That reinforced something I already suspected:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Visual projects are much easier to explain at a glance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;People may not care about the solver at first. They care because it looks interesting. Then some of them become curious about how it works.&lt;/p&gt;

&lt;p&gt;That is a good entry point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;There are prebuilt binaries for macOS, Windows, and Linux in the GitHub releases.&lt;/p&gt;

&lt;p&gt;On macOS, it can also be installed with Homebrew:&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; &lt;span class="nt"&gt;--cask&lt;/span&gt; crgimenes/tap/kutta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux, Ebitengine needs the usual Cgo/OpenGL/audio development dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controls
&lt;/h2&gt;

&lt;p&gt;Some useful controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up / Down: change angle of attack;&lt;/li&gt;
&lt;li&gt;Tab: cycle NACA profile;&lt;/li&gt;
&lt;li&gt;V: change visualization mode;&lt;/li&gt;
&lt;li&gt;S: toggle streamlines;&lt;/li&gt;
&lt;li&gt;G: toggle glow/bloom;&lt;/li&gt;
&lt;li&gt;Space: pause/resume;&lt;/li&gt;
&lt;li&gt;N: single step while paused;&lt;/li&gt;
&lt;li&gt;R: reset the flow;&lt;/li&gt;
&lt;li&gt;E: open the editor;&lt;/li&gt;
&lt;li&gt;O: open an &lt;code&gt;.afoil&lt;/code&gt; scene;&lt;/li&gt;
&lt;li&gt;Cmd+S / Ctrl+S: save.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also type a NACA code directly in the field at the top.&lt;/p&gt;

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

&lt;p&gt;The main lesson was that a small, constrained simulation can still be valuable.&lt;/p&gt;

&lt;p&gt;It does not need to solve the full real-world problem. It only needs to make one layer of the problem visible.&lt;/p&gt;

&lt;p&gt;For Kutta, that layer is aerodynamic intuition.&lt;/p&gt;

&lt;p&gt;It also reminded me that open source projects benefit from being demonstrable. A project that produces an interesting image, sound, animation, or interaction is much easier to share than a library that only makes sense after reading the API.&lt;/p&gt;

&lt;p&gt;That does not make visual projects better, but it makes them easier to communicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible next steps
&lt;/h2&gt;

&lt;p&gt;Some ideas I may explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better scene examples;&lt;/li&gt;
&lt;li&gt;more airfoil presets;&lt;/li&gt;
&lt;li&gt;improved editor workflow;&lt;/li&gt;
&lt;li&gt;better force visualization;&lt;/li&gt;
&lt;li&gt;exportable screenshots or short clips;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to keep the project small. The goal is not to turn it into a professional CFD package. The goal is to keep it useful, understandable, and fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;Kutta is open source under the MIT license:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crgimenes/kutta" rel="noopener noreferrer"&gt;https://github.com/crgimenes/kutta&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like small visual tools written in Go, take a look at the repository. A star helps me understand which experiments are worth maintaining.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Building desktop WebView apps in Go without CGo</title>
      <dc:creator>Cesar Gimenes</dc:creator>
      <pubDate>Tue, 30 Jun 2026 03:54:59 +0000</pubDate>
      <link>https://dev.to/crgimenes/building-desktop-webview-apps-in-go-without-cgo-47bj</link>
      <guid>https://dev.to/crgimenes/building-desktop-webview-apps-in-go-without-cgo-47bj</guid>
      <description>&lt;p&gt;I have been working on &lt;a href="https://github.com/crgimenes/glaze" rel="noopener noreferrer"&gt;Glaze&lt;/a&gt;, a small desktop WebView toolkit for Go.&lt;/p&gt;

&lt;p&gt;The short version: Glaze lets a Go program open a native desktop window backed by the WebView already available on the operating system, without using CGo.&lt;/p&gt;

&lt;p&gt;It currently targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS, through WKWebView&lt;/li&gt;
&lt;li&gt;Linux, through WebKitGTK&lt;/li&gt;
&lt;li&gt;Windows, through WebView2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is still young, but the core idea is already useful: keep small Go desktop tools close to the normal Go workflow.&lt;/p&gt;

&lt;p&gt;No C compiler in the build path.&lt;br&gt;
No bundled native helper library.&lt;br&gt;
No large application framework around it.&lt;/p&gt;

&lt;p&gt;Just Go code calling the system WebView.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I wanted this
&lt;/h2&gt;

&lt;p&gt;I write a lot of small tools in Go.&lt;/p&gt;

&lt;p&gt;Some of them are fine as CLI programs. Others need a basic interface: a form, a preview, a local dashboard, a small editor, or a way to inspect and manipulate data visually.&lt;/p&gt;

&lt;p&gt;For those cases, HTML is often enough. The browser gives me layout, text rendering, forms, tables, keyboard handling, and a familiar debugging model.&lt;/p&gt;

&lt;p&gt;But I do not always want to ship a web server as the user interface. I also do not always want to pull in a large desktop framework when all I need is a native window around a local UI.&lt;/p&gt;

&lt;p&gt;A WebView is a reasonable middle ground.&lt;/p&gt;

&lt;p&gt;The problem is that many WebView solutions eventually bring CGo, native build tooling, helper libraries, or larger framework assumptions into the project.&lt;/p&gt;

&lt;p&gt;That is not necessarily wrong. For many applications, those trade-offs are acceptable.&lt;/p&gt;

&lt;p&gt;For this project, I wanted something narrower.&lt;/p&gt;
&lt;h2&gt;
  
  
  The design constraint
&lt;/h2&gt;

&lt;p&gt;The main constraint behind Glaze is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the WebView already provided by the OS, but call it from Go without CGo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Glaze uses &lt;a href="https://github.com/ebitengine/purego" rel="noopener noreferrer"&gt;purego&lt;/a&gt; to call native platform APIs directly from Go.&lt;/p&gt;

&lt;p&gt;That means each backend talks to the platform WebView:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WKWebView on macOS&lt;/li&gt;
&lt;li&gt;WebKitGTK on Linux&lt;/li&gt;
&lt;li&gt;WebView2 on Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is not a full GUI toolkit. That is intentional.&lt;/p&gt;

&lt;p&gt;Glaze is focused on the window, the WebView, JavaScript-to-Go bindings, and a few desktop helpers that are useful for small tools.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hello world
&lt;/h2&gt;

&lt;p&gt;A minimal Glaze program looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/crgimenes/glaze"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;glaze&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Glaze"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glaze&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HintNone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Hello from Glaze&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&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 opens a native window and renders HTML inside the system WebView.&lt;/p&gt;
&lt;h2&gt;
  
  
  A local Go app in a desktop window
&lt;/h2&gt;

&lt;p&gt;One of the patterns I care about most is turning an existing &lt;code&gt;net/http&lt;/code&gt; application into a desktop app with minimal changes.&lt;/p&gt;

&lt;p&gt;Glaze has an &lt;code&gt;AppWindow&lt;/code&gt; helper for that.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/crgimenes/glaze"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/html; charset=utf-8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;`&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;
    &amp;lt;title&amp;gt;Glaze App&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            font-family: system-ui, sans-serif;
            margin: 2rem;
        }
        button {
            font: inherit;
            padding: 0.5rem 1rem;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Local Go UI&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This page is served by a Go http.Handler and displayed in a desktop WebView.&amp;lt;/p&amp;gt;
    &amp;lt;button onclick="location.reload()"&amp;gt;Reload&amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;`&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"write response: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;glaze&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glaze&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"Glaze App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the style of application I had in mind: local tools, internal utilities, small editors, dashboards, inspectors, and experiments.&lt;/p&gt;

&lt;p&gt;The UI can still be plain HTML, CSS, and JavaScript. The backend can still be normal Go.&lt;/p&gt;
&lt;h2&gt;
  
  
  Calling Go from JavaScript
&lt;/h2&gt;

&lt;p&gt;Glaze also supports binding Go functions to JavaScript.&lt;/p&gt;

&lt;p&gt;That makes it possible to keep the UI lightweight while still doing real work in Go.&lt;/p&gt;

&lt;p&gt;The goal is not to turn every application into a complex frontend. The useful case is exposing a small surface area: save a file, query local state, run a calculation, trigger an operation, or send an event.&lt;/p&gt;

&lt;p&gt;This fits well with small tools where most of the logic belongs in Go and the WebView is mainly the presentation layer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Glaze is not trying to be
&lt;/h2&gt;

&lt;p&gt;Glaze is not trying to replace mature GUI toolkits.&lt;/p&gt;

&lt;p&gt;It is also not trying to replace larger frameworks for building full desktop applications.&lt;/p&gt;

&lt;p&gt;Those projects usually solve broader problems: packaging, application lifecycle, icons, installers, auto-update, system integration, custom controls, and more.&lt;/p&gt;

&lt;p&gt;Glaze is intentionally smaller.&lt;/p&gt;

&lt;p&gt;The target use case is closer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“I have a Go tool and want a small local UI”&lt;/li&gt;
&lt;li&gt;“I already have an HTTP handler and want to show it in a desktop window”&lt;/li&gt;
&lt;li&gt;“I want HTML for layout but do not want to ship a browser”&lt;/li&gt;
&lt;li&gt;“I want to avoid CGo in this project”&lt;/li&gt;
&lt;li&gt;“I want a thin WebView layer instead of a full application framework”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That scope keeps the API easier to reason about.&lt;/p&gt;
&lt;h2&gt;
  
  
  Platform notes
&lt;/h2&gt;

&lt;p&gt;macOS is the cleanest case because the Cocoa and WebKit frameworks are already part of the system.&lt;/p&gt;

&lt;p&gt;Windows depends on the Microsoft Edge WebView2 Runtime, which is already present on current Windows installations in many cases.&lt;/p&gt;

&lt;p&gt;Linux is the hard case. Different distributions package WebKitGTK differently, and dynamic library names matter. Glaze tries to detect GTK4/WebKitGTK first and falls back to GTK3/WebKitGTK where appropriate, but Linux still needs more testing across distributions.&lt;/p&gt;

&lt;p&gt;That is one of the areas where feedback would be useful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Current features
&lt;/h2&gt;

&lt;p&gt;At the moment Glaze includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;desktop WebView windows&lt;/li&gt;
&lt;li&gt;JavaScript-to-Go binding&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BindMethods&lt;/code&gt; helper for exposing exported Go methods&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RenderHTML&lt;/code&gt; helper for Go templates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AppWindow&lt;/code&gt; for local &lt;code&gt;net/http&lt;/code&gt; apps&lt;/li&gt;
&lt;li&gt;Go-to-JavaScript event bridge&lt;/li&gt;
&lt;li&gt;native file dialogs&lt;/li&gt;
&lt;li&gt;native menu support on macOS and Windows&lt;/li&gt;
&lt;li&gt;runnable visual examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository also includes small demos such as Game of Life, Starfield, Doom Fire, Mandelbrot, Falling Sand, Raycasting, and a Filo REPL.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/crgimenes" rel="noopener noreferrer"&gt;
        crgimenes
      &lt;/a&gt; / &lt;a href="https://github.com/crgimenes/glaze" rel="noopener noreferrer"&gt;
        glaze
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Glaze: a CGo-free desktop WebView toolkit for Go
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Glaze&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Glaze is a desktop WebView binding for Go. It is a pure-Go port of &lt;a href="https://github.com/webview/webview" rel="noopener noreferrer"&gt;webview/webview&lt;/a&gt; built on &lt;a href="https://github.com/ebitengine/purego" rel="noopener noreferrer"&gt;purego&lt;/a&gt;, keeping CGo out of the picture. Each backend talks to the WebView framework the OS already ships -- WKWebView on macOS, WebKitGTK on Linux, WebView2 on Windows -- so nothing native is bundled.&lt;/p&gt;

&lt;p&gt;It started as a fork of &lt;code&gt;go-webview&lt;/code&gt; but has diverged enough to live as a separate codebase with its own goals and API.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Examples&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Desktop&lt;/th&gt;

&lt;th&gt;Game of Life&lt;/th&gt;

&lt;th&gt;Starfield&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/desktop/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fdesktop.gif" alt="Desktop example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/gameoflife/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fgameoflife.gif" alt="Game of Life example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/starfield/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fstarfield.gif" alt="Starfield example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Doom Fire&lt;/th&gt;

&lt;th&gt;Mandelbrot&lt;/th&gt;

&lt;th&gt;Falling Sand&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/doomfire/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fdoomfire.gif" alt="Doom Fire example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/mandelbrot/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fmandelbrot.gif" alt="Mandelbrot example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/fallingsand/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Ffallingsand.gif" alt="Falling Sand example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Raycasting&lt;/th&gt;

&lt;th&gt;Filo REPL&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/raycasting/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Fraycasting.gif" alt="Raycasting example preview"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a href="https://github.com/crgimenes/glaze/examples/filorepl/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcrgimenes%2Fglaze%2FHEAD%2Fimgs%2Ffilorepl.gif" alt="Filo REPL example preview"&gt;&lt;/a&gt;&lt;/td&gt;


&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why no CGo&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;This is the whole point of the project, and it's the part that's easy to miss. Most native-WebView bindings reach for CGo, which quietly takes back the things that make Go pleasant to ship: cross-compiling suddenly needs a matching &lt;strong&gt;C&lt;/strong&gt; cross-compiler for every target (mingw for Windows, a sysroot for Linux), builds stop being reproducible, and &lt;code&gt;go&lt;/code&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/crgimenes/glaze" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;The main benefit is keeping the project close to regular Go development.&lt;/p&gt;

&lt;p&gt;The main cost is that Glaze sits close to native platform APIs, so platform differences are real.&lt;/p&gt;

&lt;p&gt;Some trade-offs are deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not bundle a browser.&lt;/li&gt;
&lt;li&gt;It does not try to hide every OS difference.&lt;/li&gt;
&lt;li&gt;It does not try to become a complete desktop framework.&lt;/li&gt;
&lt;li&gt;Linux support depends on system WebKitGTK libraries.&lt;/li&gt;
&lt;li&gt;Windows support depends on WebView2 runtime behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my use case, that is acceptable. I prefer a small, explicit layer over a large abstraction that hides too much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I want to take it
&lt;/h2&gt;

&lt;p&gt;The immediate focus is stability and examples.&lt;/p&gt;

&lt;p&gt;The most valuable improvements right now are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing on more Linux distributions&lt;/li&gt;
&lt;li&gt;better diagnostics when system WebView libraries are missing&lt;/li&gt;
&lt;li&gt;more examples showing realistic local tools&lt;/li&gt;
&lt;li&gt;API feedback from Go developers who build small desktop utilities&lt;/li&gt;
&lt;li&gt;clearer documentation for platform-specific behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am also interested in keeping the project boring in the good sense: small API, predictable behavior, explicit errors, and no unnecessary magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The project is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crgimenes/glaze" rel="noopener noreferrer"&gt;https://github.com/crgimenes/glaze&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback is welcome, especially around the API shape, Linux/WebKitGTK behavior, and whether the examples make the intended use case clear.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
