<?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: Art</title>
    <description>The latest articles on DEV Community by Art (@artanidos).</description>
    <link>https://dev.to/artanidos</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%2F3817798%2Fc8737449-667c-4095-b2ec-393e198fbfb4.jpg</url>
      <title>DEV Community: Art</title>
      <link>https://dev.to/artanidos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/artanidos"/>
    <language>en</language>
    <item>
      <title>A free textbook about Forge4D in the making: native apps from two small text files</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Wed, 21 Oct 2026 14:26:51 +0000</pubDate>
      <link>https://dev.to/artanidos/a-free-textbook-in-the-making-native-apps-from-two-small-text-files-409f</link>
      <guid>https://dev.to/artanidos/a-free-textbook-in-the-making-native-apps-from-two-small-text-files-409f</guid>
      <description>&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%2Fwj8fjn2lwc3a4qcvtfxh.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%2Fwj8fjn2lwc3a4qcvtfxh.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
This is a whole app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sml"&gt;&lt;code&gt;&lt;span class="n"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mainWindow&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"My First Forge App"&lt;/span&gt;
    &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;320&lt;/span&gt;

    &lt;span class="n"&gt;VBoxContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;anchors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt;
        &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
        &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;

        &lt;span class="n"&gt;Label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Hello, Forge!"&lt;/span&gt; &lt;span class="n"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;btnHello&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Say hello"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;clicks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;btnHello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clicks&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello for the ${clicks}. time!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first file says &lt;strong&gt;what is there&lt;/strong&gt;. The second says &lt;strong&gt;what should happen&lt;/strong&gt;. There is no main function, no event loop and no listener registration, and you don't have to install anything by hand. &lt;code&gt;./run.sh url file://…/app.sml&lt;/code&gt; opens a native window on macOS or Linux, and before it appears the script has already been compiled to machine code.&lt;/p&gt;

&lt;p&gt;I'm writing a textbook about this, and while I write it, you can read it for free.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Forge4D&lt;/strong&gt; is an open source app platform built on the Godot engine. You describe the user interface in &lt;strong&gt;SML&lt;/strong&gt;, a small markup language of nodes and properties. You describe the behaviour in &lt;strong&gt;SMS&lt;/strong&gt;, a small Kotlin-flavoured scripting language that is compiled ahead of time through LLVM. Together they form what we call &lt;strong&gt;ForgeIL – the Intention Language&lt;/strong&gt;: you write down intentions, and the runner takes care of the how.&lt;/p&gt;

&lt;p&gt;The book is called &lt;em&gt;Forge4D – Building Apps with ForgeIL, the Intention Language&lt;/em&gt;. It is hands-on. Every chapter has a goal, a complete example, a line-by-line walk-through, a list of pitfalls, an exercise, and a solution in the appendix.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is out today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part I – Intention:&lt;/strong&gt; why an intention language, and your first running app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part II – Structure (SML), first three chapters:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Nodes, Properties and Values&lt;/em&gt;: the whole syntax, built around a small business card app&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Layout&lt;/em&gt;: containers instead of coordinates, size flags, split views, grids, tabs, and anchors for the rare element that should float&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Resources and Themes&lt;/em&gt;: one &lt;code&gt;strings.sml&lt;/code&gt; per language, colour tokens by role, named styles, and switching between a dark and a light theme at runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coming next: Markdown inside SML, the element catalogue, and Part III on SMS: values, control flow, functions, events, files, and non-blocking calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every example runs, and that's not a slogan
&lt;/h2&gt;

&lt;p&gt;Each example in the book is started in the real Forge Runner, clicked through and screenshotted before it goes into a chapter. That habit has paid off quickly.&lt;/p&gt;

&lt;p&gt;While I was writing the SMS chapters, the tests turned up a list of things the native compiler did not handle correctly yet: strings passed through functions, floating-point math, some loops. So we stopped writing and fixed the compiler first. The new compiler infers types and is checked by a conformance suite that runs every test both in the interpreter and as native code. The book waited for it.&lt;/p&gt;

&lt;p&gt;The SML chapters found smaller things. One example: &lt;code&gt;padding&lt;/code&gt; takes its four values as left, top, right, bottom, not in the CSS order you probably expect. The book now says so, and every pitfall in it comes from a real run like that one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read it
&lt;/h2&gt;

&lt;p&gt;📖 &lt;strong&gt;&lt;a href="https://crowdbooks.crowdware.info/books/forge4d-c2c815" rel="noopener noreferrer"&gt;Forge4D on CrowdBooks&lt;/a&gt;&lt;/strong&gt;: free while it is being written&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source code of the platform: &lt;a href="https://codeberg.org/CrowdWare/Forge4D" rel="noopener noreferrer"&gt;codeberg.org/CrowdWare/Forge4D&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;All book examples: &lt;a href="https://codeberg.org/CrowdWare/books/src/branch/main/forge4d/examples" rel="noopener noreferrer"&gt;codeberg.org/CrowdWare/books&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need a Mac with Apple Silicon or a Linux PC, &lt;code&gt;git&lt;/code&gt;, and a text editor. &lt;code&gt;git clone&lt;/code&gt;, &lt;code&gt;./run.sh&lt;/code&gt;, and the runner downloads itself on first use.&lt;/p&gt;

&lt;p&gt;If you try it and something in a chapter doesn't work the way it says, I want to know. It's an early edition, and your feedback goes straight into the next chapter.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;– Adam Art Ananda&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>programmers</category>
      <category>beginners</category>
      <category>godot</category>
    </item>
    <item>
      <title>A community marketplace without a database or payment provider: the Ğ1 (June)-Markt</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Sat, 03 Oct 2026 09:33:02 +0000</pubDate>
      <link>https://dev.to/artanidos/a-community-marketplace-without-a-database-or-payment-provider-the-june-markt-4gla</link>
      <guid>https://dev.to/artanidos/a-community-marketplace-without-a-database-or-payment-provider-the-june-markt-4gla</guid>
      <description>&lt;p&gt;Today the &lt;strong&gt;June-Markt&lt;/strong&gt; went live on &lt;a href="https://crowdware.info/markt" rel="noopener noreferrer"&gt;crowdware.info/markt&lt;/a&gt;. It's a marketplace for the community around June (Ğ1), a free currency. Members offer honey, massages, websites or guitar lessons, post requests such as "band looking for a guitarist", and pay each other directly from wallet to wallet.&lt;/p&gt;

&lt;p&gt;It runs on &lt;a href="https://crowdware.info/forgecms" rel="noopener noreferrer"&gt;ForgeCMS&lt;/a&gt;, my small CMS written in Go, as a new module. There's no database, no payment provider, and the platform never holds a single coin of anybody's money. The operator can still charge a fee on sales.&lt;/p&gt;

&lt;p&gt;This morning I bought a 1 Ğ1 test item from my own listing. About 90 seconds after the transfer, the order was marked as paid, both emails had gone out, and 0.03 Ğ1 of market fee was booked on the vendor's fee account.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;For operators:&lt;/strong&gt; &lt;a href="https://crowdware.info/market-module" rel="noopener noreferrer"&gt;https://crowdware.info/market-module&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a market for June?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duniter.org/g1/" rel="noopener noreferrer"&gt;Ğ1&lt;/a&gt; has been running since 2017. Every member creates the same small share of new money each day, the &lt;em&gt;universal dividend&lt;/em&gt;, so it works a bit like a basic income built into the currency. Since March 2026 it runs on Duniter v2, a Substrate-based blockchain.&lt;/p&gt;

&lt;p&gt;A currency is only as useful as the things you can buy with it. Ğchange, a classifieds site for June, already exists. I wanted something that fits into local websites, for example one market per association. I also wanted the site to see payments by itself, so it can do more than show ads.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;For visitors:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a list of listings with photo, price, place and hashtags, sorted into goods, services and requests ("wanted")&lt;/li&gt;
&lt;li&gt;search by text, postcode or place and kind, plus &lt;strong&gt;hashtags&lt;/strong&gt; like &lt;code&gt;#honig&lt;/code&gt;, &lt;code&gt;#massage&lt;/code&gt;, &lt;code&gt;#bandsuchtgitarist&lt;/code&gt;, with a tag cloud and a few quick filters chosen by the operator&lt;/li&gt;
&lt;li&gt;a contact form on every listing that mails the vendor and keeps the vendor's address hidden&lt;/li&gt;
&lt;li&gt;a "report" button on every listing, because the EU Digital Services Act requires a notice-and-action mechanism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;direct purchase&lt;/strong&gt; on listings that allow it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For vendors:&lt;/strong&gt; login by email link (no password), a profile with an optional Ğ1 wallet, up to four photos per listing, Markdown descriptions, and pause, renew and delete with one click. Listings go offline after 90 days, with a reminder a week before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For the operator:&lt;/strong&gt; moderation (the first listing of every new vendor waits for approval), reports, blocking, and a fee on direct sales.&lt;/p&gt;

&lt;p&gt;Like everything in ForgeCMS, it's server-rendered HTML with Bootstrap classes and no JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashtags instead of categories
&lt;/h2&gt;

&lt;p&gt;My first plan had fixed categories in the config. Then I realised a community market is too varied for that. Someone selling honey, someone offering a massage and someone looking for a band don't fit into one category tree. So every listing gets one to five free hashtags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Software, massage #Mann_sucht_Frau
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are normalised to lower case, without &lt;code&gt;#&lt;/code&gt;, and limited to 2–30 Unicode letters, digits or &lt;code&gt;_&lt;/code&gt;. The list filters by any combination of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ✓ badge: trust from the Web of Trust
&lt;/h2&gt;

&lt;p&gt;Ğ1 members are certified by other members, so the currency has a Web of Trust built in. The public indexer tells you whether a wallet belongs to a certified member:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="err"&gt;accounts(filter:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vendors whose wallet belongs to a member get a "✓ Ğ1 member: name" badge. It doesn't guarantee the offer, but on a community market it is a strong signal against fake accounts. A new wallet only shows up after the vendor confirms it by email, so a stolen session cookie can't silently redirect payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct purchase: the money never touches the server
&lt;/h2&gt;

&lt;p&gt;This was the hard design question. I wanted the operator to earn a fee, say 3 %. The obvious way is that the platform collects the money and forwards 97 % to the vendor. That has two problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The server would need a private key, so a hacked server would mean stolen money.&lt;/li&gt;
&lt;li&gt;In Germany, collecting and forwarding other people's money is regulated as a payment service. That's not something a small community site wants to deal with.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the buyer pays the &lt;strong&gt;vendor's wallet&lt;/strong&gt; directly, and the fee is billed afterwards:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The buyer orders on the listing page and gets an order number, e.g. &lt;code&gt;MK-EZMDY3GY&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;They pay the vendor's wallet with that number in the transfer comment.&lt;/li&gt;
&lt;li&gt;A watcher goroutine sees the payment, marks the order paid, books &lt;code&gt;amount × 3 %&lt;/code&gt; on the vendor's fee account, and mails both sides.&lt;/li&gt;
&lt;li&gt;On the 1st of every month each vendor gets a statement &lt;code&gt;FEE-…&lt;/code&gt; and pays it to the market wallet, again with the number in the comment. Totals under 1 Ğ1 are carried over.&lt;/li&gt;
&lt;li&gt;If a statement is still unpaid 30 days later, the vendor's listings are hidden until it is paid.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vendors opt in per listing. The "allow direct purchase" checkbox also accepts the fee, so existing listings stay contact-only and fee-free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watching many wallets with one query
&lt;/h3&gt;

&lt;p&gt;The shop module watches one wallet. The market has to watch every vendor wallet with an open order, plus the market wallet while a statement is open. The Duniter indexer is PostGraphile, so one &lt;code&gt;in&lt;/code&gt; filter covers all of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="err"&gt;transfers(orderBy:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;BLOCK_NUMBER_ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;filter:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;toId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;wallets&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                   &lt;/span&gt;&lt;span class="n"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lessThanOrEqualTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fromId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once a minute the watcher reads the blocks since the last run, minus 10 confirmations. An order ID only counts on a transfer to &lt;em&gt;that order's&lt;/em&gt; wallet. A statement ID only counts on the market wallet. Partial payments add up, a transfer is never booked twice, and the last processed block lives in a small state file. Vendor wallets get plenty of unrelated transfers, and those are simply ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  No database, still
&lt;/h2&gt;

&lt;p&gt;Everything is SML, my own small config format, written atomically next to the binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sml"&gt;&lt;code&gt;&lt;span class="n"&gt;Listing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"k3f9qxab"&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"goods"&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Blütenhonig 500 g"&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"honig lebensmittel"&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;          &lt;span class="n"&gt;//&lt;/span&gt; &lt;span class="n"&gt;centimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;8.00&lt;/span&gt; &lt;span class="err"&gt;Ğ&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Glas"&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt;
    &lt;span class="n"&gt;buy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"k3f9qxab-abcdefgh.jpg"&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;On start the store loads everything into memory, and every change is a temp file plus rename under one mutex. For a community market with hundreds of listings that's plenty, and backups are just &lt;code&gt;rsync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I did hit one classic bug. The list functions called a filter callback while holding the store mutex. When the filter started asking "is this vendor's fee overdue?", which needs the same mutex, the test froze. Now the store copies under the lock and filters outside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  User content is untrusted content
&lt;/h2&gt;

&lt;p&gt;A few things that matter more on a marketplace than on a normal site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Photos&lt;/strong&gt; are decoded one at a time (to protect a small VPS from memory spikes), turned upright using the EXIF orientation, scaled to 1600 px and re-encoded. That drops all metadata, including the GPS position of your kitchen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown&lt;/strong&gt; in listings goes through its own goldmark renderer, not the site's. It drops raw HTML, empties &lt;code&gt;javascript:&lt;/code&gt; links, adds &lt;code&gt;rel="nofollow ugc noopener"&lt;/code&gt;, turns images into plain links so nothing third-party gets loaded, and starts headings at &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The contact form&lt;/strong&gt; doesn't send a copy to the sender. Otherwise anyone could use it to mail arbitrary text to arbitrary addresses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removing a listing or blocking a vendor&lt;/strong&gt; mails the vendor the reason, as Art. 17 DSA requires. A reply to that mail goes to the moderator.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The market:&lt;/strong&gt; &lt;a href="https://crowdware.info/markt" rel="noopener noreferrer"&gt;https://crowdware.info/markt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For operators:&lt;/strong&gt; &lt;a href="https://crowdware.info/market-module" rel="noopener noreferrer"&gt;https://crowdware.info/market-module&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is June?&lt;/strong&gt; &lt;a href="https://crowdware.info/june" rel="noopener noreferrer"&gt;https://crowdware.info/june&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ForgeCMS source (GPLv3):&lt;/strong&gt; &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/ForgeCMS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The market module is a one-time license for &lt;strong&gt;€49 or 49 June (Ğ1)&lt;/strong&gt;. If your association, village or community wants its own market, &lt;a href="https://crowdware.info/process" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already trade in June, post something on the June-Markt. I'd love to see what turns up under &lt;code&gt;#&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>community</category>
    </item>
    <item>
      <title>A CMS with no database and no build step: your site is just a Git repo</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Wed, 30 Sep 2026 06:55:05 +0000</pubDate>
      <link>https://dev.to/artanidos/a-cms-with-no-database-and-no-build-step-your-site-is-just-a-git-repo-3855</link>
      <guid>https://dev.to/artanidos/a-cms-with-no-database-and-no-build-step-your-site-is-just-a-git-repo-3855</guid>
      <description>&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%2Fm40lwevubaliwud489e1.jpg" 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%2Fm40lwevubaliwud489e1.jpg" alt=" " width="800" height="291"&gt;&lt;/a&gt;&lt;br&gt;
Recently Angela, a musician, sent me the link to her new website: &lt;a href="https://music.angela-mahr.de" rel="noopener noreferrer"&gt;music.angela-mahr.de&lt;/a&gt;. She built it herself. She didn't use a page builder or a WordPress theme. She told Claude Code what she wanted, and it wrote the pages for &lt;strong&gt;ForgeCMS&lt;/strong&gt;, the small CMS I've been building since March. Then she built a second one: &lt;a href="https://lebe-dich.info" rel="noopener noreferrer"&gt;lebe-dich.info&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's when I realized the design decisions behind ForgeCMS might be interesting to other developers too. This post explains how it works and why it's built this way.&lt;/p&gt;
&lt;h2&gt;
  
  
  The whole architecture in one line
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Caddy (TLS) → forgecms → Codeberg (SML + Markdown)
                                   ↘ local cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;ForgeCMS is a single Go binary. Your site is a folder in a public Git repo: pages in SML, longer texts in Markdown, images in &lt;code&gt;static/&lt;/code&gt;. When a request comes in, ForgeCMS fetches the files it needs from Codeberg, caches them, and renders HTML.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No database.&lt;/strong&gt; Your history, backups and reviews all happen in Git.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No build step.&lt;/strong&gt; Commit a change (even in Codeberg's web editor), and it's live within a few minutes. There's no CI pipeline and nothing to regenerate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No admin backend&lt;/strong&gt; to patch, and no plugin ecosystem to keep compatible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One process, many domains.&lt;/strong&gt; Put one config per domain in &lt;code&gt;sites/&amp;lt;domain&amp;gt;/app.sml&lt;/code&gt;, and ForgeCMS switches to multi-site mode. Our production box serves eight sites from one process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dependencies are the Go standard library, &lt;a href="https://github.com/yuin/goldmark" rel="noopener noreferrer"&gt;goldmark&lt;/a&gt; for Markdown (with chroma for code highlighting), and &lt;a href="https://codeberg.org/CrowdWare/sml-go" rel="noopener noreferrer"&gt;sml-go&lt;/a&gt; for the page language.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pages in SML, not JSON or YAML
&lt;/h2&gt;

&lt;p&gt;Pages are written in SML: elements with properties and children. Here's a complete page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page {
    title: "My Bakery"

    Heading { level: "1" text: "Fresh bread, every morning" }
    Markdown { src: "main.md" }

    Cards {
        Card { title: "Sourdough" text: "Baked daily from a 12-year-old starter." }
        Card { title: "Opening hours" text: "Tue–Sat, 7:00–13:00" }
    }

    ContactForm { }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need HTML, CSS or JavaScript. SML is also a format that language models get right on the first try. It has no indentation rules to break, no quoting puzzles like YAML, and no trailing-comma errors like JSON. Every element is written the same way, so a model that has seen three examples can write a fourth.&lt;/p&gt;

&lt;p&gt;That's the real reason Angela could build her site. The repo contains an &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS/src/branch/main/AI.md" rel="noopener noreferrer"&gt;AI.md&lt;/a&gt; that explains the whole system to a coding agent: the elements, the folder layout, how languages work, and how to deploy. You point Claude Code at it and describe the site you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Why not a static site generator?"
&lt;/h2&gt;

&lt;p&gt;I get this question a lot, and it's a fair one. Hugo and friends are great. What bothered me is the part between "I changed a sentence" and "it's live": a build, a pipeline, an artifact, a deploy. For the people I build sites for (small associations, therapists, musicians), that pipeline is exactly where things break and where they need me.&lt;/p&gt;

&lt;p&gt;With ForgeCMS the repo &lt;em&gt;is&lt;/em&gt; the deployment. Anyone who can edit a file on Codeberg can update their site. If they break the syntax, the last good version keeps being served. A typo never takes the site down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned the hard way: caching against a Git host
&lt;/h2&gt;

&lt;p&gt;Fetching content from Codeberg at request time sounds fragile, and the first version was. On September 25, after a routine restart, a burst of bot traffic made ForgeCMS fetch the same &lt;code&gt;404.sml&lt;/code&gt; about 800 times. Codeberg allows 250 raw-file requests per 10 minutes per IP. We got a &lt;code&gt;429&lt;/code&gt; with &lt;code&gt;Retry-After: 600&lt;/code&gt;, and three sites returned &lt;code&gt;503&lt;/code&gt; for about 20 minutes.&lt;/p&gt;

&lt;p&gt;The fix was three rules that I now think every "fetch from origin" system needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Revalidate at most every 5 minutes&lt;/strong&gt; per file, no matter how many requests come in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use one shared backoff for the whole origin&lt;/strong&gt;, and respect &lt;code&gt;Retry-After&lt;/code&gt;. When Codeberg says "wait", every file waits, not just the one that got the 429.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always serve the stale copy&lt;/strong&gt; when the origin fails. A slightly old page is better than an error page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those rules, Codeberg can have a bad day and visitors don't notice. The same fallback also works for static files: if an image isn't deployed on the server, ForgeCMS fetches it from the repo's &lt;code&gt;static/&lt;/code&gt; folder. This morning I added the banner at the top of this post to our developer page with nothing but a &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules for the boring-but-necessary parts
&lt;/h2&gt;

&lt;p&gt;The core covers pages, menus, themes, several languages per site (a &lt;code&gt;de/&lt;/code&gt;, &lt;code&gt;es/&lt;/code&gt;, … subfolder with fallback), per-page SEO descriptions and embeds. The things that need server-side state are modules in the same binary. You switch them on with a block in &lt;code&gt;app.sml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blog&lt;/strong&gt; with photos, Atom feed and share buttons without trackers (&lt;a href="https://art.ubuntu-pirates.com" rel="noopener noreferrer"&gt;live&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events&lt;/strong&gt; with flyers, event pages and &lt;code&gt;.ics&lt;/code&gt; export (&lt;a href="https://crowdware.info/events-demo" rel="noopener noreferrer"&gt;demo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contact form and newsletter&lt;/strong&gt; with form fields defined in SML, double opt-in and mailings written in Markdown (&lt;a href="https://crowdware.info/contact" rel="noopener noreferrer"&gt;live&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shop&lt;/strong&gt; paid in Ğ1 or euros, with digital delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature requests&lt;/strong&gt; with voting and magic-link login (&lt;a href="https://crowdware.info/forgecms/features" rel="noopener noreferrer"&gt;live&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever these modules need to store, like orders or subscribers, is written as small SML files on the server. There's still no database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quickstart
&lt;/h2&gt;

&lt;p&gt;You need Go 1.22 or newer and a public repo on Codeberg for your content.&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 https://codeberg.org/CrowdWare/ForgeCMS.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ForgeCMS
go build &lt;span class="nt"&gt;-o&lt;/span&gt; forgecms &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# point the Site { } block at your content repo&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;app-demo.sml app.sml

./forgecms &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put Caddy in front for HTTPS and you're done. For a real multi-site setup, our production repo is public: &lt;a href="https://codeberg.org/CrowdWare/sites" rel="noopener noreferrer"&gt;CrowdWare/sites&lt;/a&gt; contains the content, the per-domain configs and the deploy script for every site mentioned here. The page describing ForgeCMS for developers is &lt;a href="https://codeberg.org/CrowdWare/sites/src/branch/main/crowdware/forgecms-developers.sml" rel="noopener noreferrer"&gt;one SML file&lt;/a&gt; in that repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;ForgeCMS is free under the GPLv3 for non-commercial use. If you make money with it, for example by hosting sites for paying customers, there's a &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS/src/branch/main/LICENSE-COMMERCIAL.md" rel="noopener noreferrer"&gt;commercial license&lt;/a&gt;. Profits aren't paid out to individuals. They go into &lt;a href="https://ubuntu-pirates.com" rel="noopener noreferrer"&gt;UBUNTU land projects&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, break it, tell me
&lt;/h2&gt;

&lt;p&gt;Everything in one place: &lt;strong&gt;&lt;a href="https://crowdware.info/forgecms/developers" rel="noopener noreferrer"&gt;crowdware.info/forgecms/developers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS" rel="noopener noreferrer"&gt;codeberg.org/CrowdWare/ForgeCMS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Missing something? &lt;a href="https://crowdware.info/forgecms/features" rel="noopener noreferrer"&gt;Propose a feature or vote&lt;/a&gt;. The most-voted requests get built first, and four of the first five are already done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd especially like to hear from people who run sites for others: what would you need before you'd move a client off WordPress?&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>ForgeCMS: a CMS without a database, and our users decide what gets built next</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Tue, 29 Sep 2026 08:03:02 +0000</pubDate>
      <link>https://dev.to/artanidos/forgecms-a-cms-without-a-database-and-our-users-decide-what-gets-built-next-5ghj</link>
      <guid>https://dev.to/artanidos/forgecms-a-cms-without-a-database-and-our-users-decide-what-gets-built-next-5ghj</guid>
      <description>&lt;p&gt;Six months ago &lt;a href="https://crowdware.info/forgecms" rel="noopener noreferrer"&gt;ForgeCMS&lt;/a&gt; was a weekend experiment: can a website run without WordPress, without a database and without a build step? Today it runs several real sites, and it covers most of what a small business, an association or a creator needs from a website.&lt;/p&gt;

&lt;p&gt;This post gives an overview of what's there, how it works, and how you can tell us what should come next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one paragraph
&lt;/h2&gt;

&lt;p&gt;ForgeCMS is a single Go binary. Your content lives as Markdown and small SML files in a Git repo on &lt;a href="https://codeberg.org" rel="noopener noreferrer"&gt;Codeberg&lt;/a&gt;. The server fetches the files at request time, caches them, and renders HTML. There's no database, no admin backend to keep patched, and no build pipeline. You edit a file in Git, and a few minutes later the change is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a page looks like
&lt;/h2&gt;

&lt;p&gt;Pages are written in SML, a simple markup of nested blocks with properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sml"&gt;&lt;code&gt;&lt;span class="n"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"My Bakery"&lt;/span&gt;

    &lt;span class="n"&gt;Heading&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Fresh bread, every morning"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Family-owned bakery in Wittenberg. Stone oven, no additives."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Cards&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Sourdough"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Baked daily from a 12-year-old starter."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;Card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Opening hours"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Tue–Sat, 7:00–13:00"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"mailto:hallo@meinebaeckerei.de"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Get in touch"&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;Longer texts are plain Markdown. You can read SML in seconds, and it turns out an AI can write it just as easily. That was a deliberate choice: many of our sites are edited by describing the change to Claude Code on the server, and it gets SML right on the first try.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's already in the box
&lt;/h2&gt;

&lt;p&gt;The free core covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pages in SML and Markdown, menu, footer, themes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;multiple languages&lt;/strong&gt;: a &lt;code&gt;de/&lt;/code&gt;, &lt;code&gt;en/&lt;/code&gt;, &lt;code&gt;es/&lt;/code&gt; … subfolder per language, with fallback to the default&lt;/li&gt;
&lt;li&gt;menu, footer and routes can live in the content repo too, so editing the site never requires touching the server&lt;/li&gt;
&lt;li&gt;sliders, cards, tables, images, YouTube embeds, privacy-friendly analytics (Umami)&lt;/li&gt;
&lt;li&gt;a content cache that keeps serving the last good copy if Git hosting is slow or down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that there are &lt;strong&gt;modules&lt;/strong&gt;, each built for a real site:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Posts with photos, pages, Atom feed, drafts and scheduled posts, share buttons without third-party scripts, several languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dates and gigs with flyer thumbnails, an event page per date, past events, &lt;code&gt;.ics&lt;/code&gt; calendar export&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contact form&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text fields, checkboxes, radio buttons, selection lists; messages go by email and are not stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Double opt-in, mailings written in Markdown in a small admin page, one-click unsubscribe (RFC 8058)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Physical and digital products, paid in &lt;a href="https://duniter.org/g1/" rel="noopener noreferrer"&gt;June (Ğ1)&lt;/a&gt; or by euro bank transfer, no payment provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feature requests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visitors propose features and vote, one vote per person&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything that needs to be stored, like orders or newsletter addresses, is stored as small SML files on the server. Still no database.&lt;/p&gt;

&lt;p&gt;A few of these are running in public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the blog: &lt;a href="https://art.ubuntu-pirates.com" rel="noopener noreferrer"&gt;art.ubuntu-pirates.com&lt;/a&gt;, a travel blog of a bike trip to Spain, written on the road&lt;/li&gt;
&lt;li&gt;the events demo: &lt;a href="https://crowdware.info/events-demo" rel="noopener noreferrer"&gt;crowdware.info/events-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the shop demo: &lt;a href="https://crowdware.info/shop-module" rel="noopener noreferrer"&gt;crowdware.info/shop-module&lt;/a&gt; (the demo orders are real, you get a handwritten postcard)&lt;/li&gt;
&lt;li&gt;contact form and newsletter: &lt;a href="https://crowdware.info/contact" rel="noopener noreferrer"&gt;crowdware.info/contact&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Login without passwords
&lt;/h2&gt;

&lt;p&gt;The modules that need a login (feature voting, newsletter admin) use &lt;strong&gt;magic links&lt;/strong&gt;. You enter your email address and get a link, and that's it. There are no passwords to leak and no accounts to manage. Login tokens live only in memory.&lt;/p&gt;

&lt;p&gt;We use the email address for one more thing: it keeps feature voting fair. One address, one vote per request, and you can take your vote back any time. Nobody sees who voted for what.&lt;/p&gt;

&lt;h2&gt;
  
  
  You decide what we build next
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most happy with. On &lt;a href="https://crowdware.info/forgecms/features" rel="noopener noreferrer"&gt;crowdware.info/forgecms/features&lt;/a&gt; anyone can propose a feature for ForgeCMS or vote for the ones they want. The requests with the most votes get built first.&lt;/p&gt;

&lt;p&gt;It works. So far five features have been proposed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contact form&lt;/strong&gt; — done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event calendar&lt;/strong&gt; — done&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blogging module&lt;/strong&gt; — done (comments are not in yet)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mailing list&lt;/strong&gt; — done, as the newsletter module&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online course&lt;/strong&gt; (lessons with videos and texts, saved progress) — open&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's four out of five done within a few days of being proposed. The online course is the next candidate, and if it matters to you, a vote helps.&lt;/p&gt;

&lt;p&gt;The voting page itself is the feature-request module, so you're testing it while you use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting: small and cheap
&lt;/h2&gt;

&lt;p&gt;ForgeCMS needs very little. All our sites share one small VPS with a single &lt;code&gt;forgecms&lt;/code&gt; process behind Caddy, which takes care of HTTPS. A €1/month VPS is enough for a small site. The &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS" rel="noopener noreferrer"&gt;README&lt;/a&gt; walks you through setting one up with Claude Code doing the work, even if you've never run a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  License and pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The core is open source under &lt;strong&gt;GPLv3&lt;/strong&gt;. For commercial use there's a commercial license.&lt;/li&gt;
&lt;li&gt;Modules are a &lt;strong&gt;one-time license&lt;/strong&gt; of €19 to €39, with no subscription. All six together cost €139.&lt;/li&gt;
&lt;li&gt;If you'd rather not do it yourself, we build the site for you at €130/hour (or 130 Ğ1). Small sites are often done in an hour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we ever make a profit, it isn't paid out to individuals. It goes into &lt;a href="https://ubuntu-pirates.com" rel="noopener noreferrer"&gt;UBUNTU land projects&lt;/a&gt; that take land out of speculation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and tell us what's missing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Propose or vote for a feature:&lt;/strong&gt; &lt;a href="https://crowdware.info/forgecms/features" rel="noopener noreferrer"&gt;https://crowdware.info/forgecms/features&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overview and modules:&lt;/strong&gt; &lt;a href="https://crowdware.info/forgecms" rel="noopener noreferrer"&gt;https://crowdware.info/forgecms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/ForgeCMS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get in touch:&lt;/strong&gt; &lt;a href="https://crowdware.info/contact" rel="noopener noreferrer"&gt;https://crowdware.info/contact&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd love to hear what you would need before you'd move a site off WordPress. The best place for that is the feature request page, because there it gets votes.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>cms</category>
    </item>
    <item>
      <title>A web shop without a database or payment provider: the ForgeCMS shop module</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Sat, 26 Sep 2026 15:14:42 +0000</pubDate>
      <link>https://dev.to/artanidos/a-web-shop-without-a-database-or-payment-provider-the-forgecms-shop-module-5e3n</link>
      <guid>https://dev.to/artanidos/a-web-shop-without-a-database-or-payment-provider-the-forgecms-shop-module-5e3n</guid>
      <description>&lt;p&gt;Today the &lt;strong&gt;shop module&lt;/strong&gt; for &lt;a href="https://crowdware.info/forgecms" rel="noopener noreferrer"&gt;ForgeCMS&lt;/a&gt; went live. Your customers can pay in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in &lt;strong&gt;June (Ğ1)&lt;/strong&gt;, a free currency, or&lt;/li&gt;
&lt;li&gt;in &lt;strong&gt;euros by plain bank transfer&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no database, no payment provider taking a cut, and no monthly fee. The money goes straight from the buyer to the seller.&lt;/p&gt;

&lt;p&gt;This afternoon I paid for my own test order with 1 Ğ1. Three minutes later the shop had marked the order as paid and sent me my download link.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;How it works for shop owners:&lt;/strong&gt; &lt;a href="https://crowdware.info/shop-module" rel="noopener noreferrer"&gt;https://crowdware.info/shop-module&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Some context: what ForgeCMS is
&lt;/h2&gt;

&lt;p&gt;ForgeCMS is a small CMS written in Go. It has no database and no build step. Pages are small SML files plus Markdown in a Git repo on Codeberg. The server fetches them at request time, caches them, and renders HTML. You edit a page in Git, and a minute later it's live.&lt;/p&gt;

&lt;p&gt;I wanted a shop that fits into this. It shouldn't need a database, it shouldn't need an account with a payment company, and it shouldn't hand buyers' data to anyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why June (Ğ1)?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://duniter.org/g1/" rel="noopener noreferrer"&gt;Ğ1&lt;/a&gt; is a libre currency that has been running since 2017. Every member creates a small share of new money each day, the &lt;em&gt;universal dividend&lt;/em&gt;, so it works a bit like a basic income built into the currency. Since March 2026 it has been running on Duniter v2, a Substrate-based blockchain. It's not a speculative token, and people in the network use it to pay for goods and services.&lt;/p&gt;

&lt;p&gt;For a shop, what matters is that &lt;strong&gt;every transfer is public and carries a comment&lt;/strong&gt;. The buyer puts the order number in the comment, and the shop can find the payment without any access to the seller's wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catalogue is one text file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sml"&gt;&lt;code&gt;&lt;span class="n"&gt;Catalog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"honey-500"&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Honey 500 g"&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"12.50"&lt;/span&gt; &lt;span class="n"&gt;priceEur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"9.90"&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"honey.jpg"&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"From our own bees."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"ebook"&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"My e-book"&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"20"&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="kt"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="kt"&gt;digital&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="kt"&gt;text:&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="kt"&gt;PDF&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;120&lt;/span&gt; &lt;span class="kt"&gt;pages&lt;/span&gt;&lt;span class="err"&gt;."&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;price&lt;/code&gt; is the price in Ğ1, and &lt;code&gt;priceEur&lt;/code&gt; the price in euros. A product with both prices lets the buyer choose. A product with only one price is sold only in that currency. On a page, &lt;code&gt;Products { }&lt;/code&gt; renders the product cards with an &lt;strong&gt;Order&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;The price always comes from the catalogue, never from the order form.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an order flows
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The buyer fills in a short form: quantity, name, email, and for physical goods a shipping address. Then they click &lt;strong&gt;Place order (payment required)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The server writes the order as a small SML file to disk, e.g. &lt;code&gt;G1-7KQ2MXRA.sml&lt;/code&gt;, and sends two emails, one to the buyer and one to the seller.&lt;/li&gt;
&lt;li&gt;The buyer sees a payment page with the address, the exact amount and the order number.&lt;/li&gt;
&lt;li&gt;The buyer pays and puts the order number into the transfer comment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Detecting Ğ1 payments
&lt;/h3&gt;

&lt;p&gt;A watcher goroutine asks the public Duniter indexer once a minute for new transfers to the shop's address. The indexer has a GraphQL API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="err"&gt;transfers(filter:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;toId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;blockNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fromId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;remark&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A transfer is matched to its order by the comment. The watcher waits 10 blocks of confirmations (about a minute) before it counts a payment. Split payments add up, and overpayments are recorded. A transfer without a matching order doesn't get lost: the seller gets an email and sorts it out by hand. The last processed block is stored in a small state file, so a restart doesn't skip or double-count anything.&lt;/p&gt;

&lt;p&gt;The server &lt;strong&gt;never holds a wallet key&lt;/strong&gt;. It only reads public data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Euro payments: one click by the seller
&lt;/h3&gt;

&lt;p&gt;For euros the shop doesn't connect to any bank. There's no banking API and nobody else sees your account. The seller's order email contains a secret confirmation link. When the money is on the account, the seller clicks it, checks the amount on the confirmation page and confirms. From there everything runs the same way as with Ğ1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Digital delivery
&lt;/h3&gt;

&lt;p&gt;For downloads, the paid email contains a license key and a personal download link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The link is valid for 30 days and 5 downloads.&lt;/li&gt;
&lt;li&gt;The server stores only a SHA-256 hash of the link's token, never the token itself.&lt;/li&gt;
&lt;li&gt;The files don't live in the public content repo. They live in a data folder on the server, and the download handler rejects path traversal and symlinks.&lt;/li&gt;
&lt;li&gt;Before ordering a digital product, the buyer ticks a box saying delivery may start immediately. German and EU law require this so the right of withdrawal ends with the download.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;no shopping cart: one product per order, any quantity&lt;/li&gt;
&lt;li&gt;no credit cards, PayPal or Stripe&lt;/li&gt;
&lt;li&gt;no stock management and no automatic invoices&lt;/li&gt;
&lt;li&gt;euro payments need one click from the seller&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small shop, a creator, a farm shop or a community project, that's usually enough. Every feature I left out is one less thing that can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tested for real
&lt;/h2&gt;

&lt;p&gt;Before going public, I ran the whole flow on the live site with a hidden test page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Euro:&lt;/strong&gt; a transfer of 0.01 EUR, confirmed with the link. The order was marked as paid and the emails went out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ğ1:&lt;/strong&gt; 1 Ğ1 for a digital test product. It was detected after 10 confirmations, the order was marked as paid, and the download link arrived by email and worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I removed the test page.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See how it works:&lt;/strong&gt; &lt;a href="https://crowdware.info/shop-module" rel="noopener noreferrer"&gt;https://crowdware.info/shop-module&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is June?&lt;/strong&gt; &lt;a href="https://crowdware.info/june" rel="noopener noreferrer"&gt;https://crowdware.info/june&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ForgeCMS source (GPLv3):&lt;/strong&gt; &lt;a href="https://codeberg.org/CrowdWare/ForgeCMS" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/ForgeCMS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shop module is a one-time &lt;strong&gt;€39&lt;/strong&gt; license, with no subscription. If you'd like a shop on your own website, whether you sell in June, in euros or both, &lt;a href="https://crowdware.info/process" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already use Ğ1, I'd love to hear how you currently sell things with it and what's missing.&lt;/p&gt;

</description>
      <category>go</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>ForgeDesigner Alpha: an AI assistant that builds websites on your own machine</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Thu, 24 Sep 2026 06:49:12 +0000</pubDate>
      <link>https://dev.to/artanidos/forgedesigner-alpha-an-ai-assistant-that-builds-websites-on-your-own-machine-2mdg</link>
      <guid>https://dev.to/artanidos/forgedesigner-alpha-an-ai-assistant-that-builds-websites-on-your-own-machine-2mdg</guid>
      <description>&lt;p&gt;Today I'm releasing the first &lt;strong&gt;alpha of ForgeDesigner&lt;/strong&gt; for &lt;strong&gt;macOS (Apple Silicon)&lt;/strong&gt; and &lt;strong&gt;Linux (x86_64)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ForgeDesigner is a small desktop app where you describe a website in plain words and an AI assistant builds it: it creates the pages, writes the Markdown, and shows you a live preview. The result is plain files in a folder on your disk. You don't need a hosting account, you don't get locked into a page builder, and nothing lives in somebody else's cloud unless you put it there.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://codeberg.org/CrowdWare/Forge4D/releases/tag/v2026.9.23-forgedesigner-linux" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/Forge4D/releases/tag/v2026.9.23-forgedesigner-linux&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I wanted something between "hand-write HTML" and "rent a website builder". The things people use today, WordPress, website builders and IDEs, all ask you to learn their world first. I wanted a single place where I can say &lt;em&gt;"make me a landing page with a short intro and a contact section"&lt;/em&gt;, then look at the files, change a sentence by hand, and ask for the next step.&lt;/p&gt;

&lt;p&gt;ForgeDesigner is built on &lt;strong&gt;&lt;a href="https://codeberg.org/CrowdWare/Forge4D" rel="noopener noreferrer"&gt;Forge 4D&lt;/a&gt;&lt;/strong&gt;, an open platform where apps are written in two small languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SML&lt;/strong&gt; (Simple Markup Language): declarative UI and content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMS&lt;/strong&gt; (Simple Script): event handlers and logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole designer UI, including the docks, the file tree, the editor and the assistant tab, is itself an SML/SMS app running on the native Forge runner (C++ on top of Godot 4.6). A few lines from its layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight qml"&gt;&lt;code&gt;&lt;span class="kt"&gt;DockingContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;rightDock&lt;/span&gt;
    &lt;span class="nl"&gt;dockSide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;

    &lt;span class="kt"&gt;VBoxContainer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;preview&lt;/span&gt;
        &lt;span class="nl"&gt;rightDock.title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabPreview&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;h2&gt;
  
  
  What works in this alpha
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI assistant with tools.&lt;/strong&gt; The assistant can &lt;code&gt;list_files&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt; and &lt;code&gt;run_command&lt;/code&gt; inside your workspace folder (&lt;code&gt;~/ForgeWorkspace/...&lt;/code&gt;). You watch it work in the transcript, and the file tree updates as files appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ForgeCMS plugin.&lt;/strong&gt; The first output target: pages as SML plus Markdown, ready to be pushed to a Git repo and served as a static site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live preview.&lt;/strong&gt; Select a page or a Markdown file and see a layout preview next to the editor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Editor that stays in sync.&lt;/strong&gt; Every keystroke is saved, and changes the assistant makes show up in the editor right away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline by default.&lt;/strong&gt; Out of the box the assistant runs against a built-in mock: it's free and sends nothing anywhere. Switching to a real model is one config line (see below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries.&lt;/strong&gt; If the model provider answers with "overloaded", the request is retried automatically; after that there's a &lt;em&gt;Retry&lt;/em&gt; button.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security model
&lt;/h2&gt;

&lt;p&gt;An AI that can write files and run commands on your machine needs guard rails. ForgeDesigner has these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions are declared and confirmed.&lt;/strong&gt; The app lists what it needs (workspace files, running programs after asking, network access to &lt;code&gt;openrouter.ai&lt;/code&gt;), and the runner asks you once on first start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands are argv, not shell strings.&lt;/strong&gt; A small set (&lt;code&gt;git&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;ssh&lt;/code&gt;, &lt;code&gt;scp&lt;/code&gt;) runs directly. Everything else opens a dialog where you decide, once or for good.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API keys stay in the runner.&lt;/strong&gt; Your key lives in a &lt;code&gt;chmod 600&lt;/code&gt; file in the runner's data folder and is never handed to the app or the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompt injection is still a real risk. The trusted commands can reach whatever your user can. That's a big part of why this is an alpha.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;macOS (Apple Silicon):&lt;/strong&gt; unzip, then remove the quarantine flag once. The alpha is only ad-hoc signed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xattr &lt;span class="nt"&gt;-dr&lt;/span&gt; com.apple.quarantine ForgeDesigner.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Linux (x86_64):&lt;/strong&gt;&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="nb"&gt;tar &lt;/span&gt;xzf ForgeDesigner-2026.9.23-linux-x86_64.tar.gz
./ForgeDesigner/ForgeDesigner.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connect a real model (OpenRouter)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a key on &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;openrouter.ai&lt;/a&gt; (&lt;em&gt;Keys → Create Key&lt;/em&gt;). Models ending in &lt;code&gt;:free&lt;/code&gt; need no credit.&lt;/li&gt;
&lt;li&gt;Open the app's data folder:

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;~/Library/Application Support/Godot/app_userdata/ForgeDesigner/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Linux: &lt;code&gt;~/.local/share/godot/app_userdata/ForgeDesigner/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Put the key into &lt;code&gt;forge_ai_keys.sml&lt;/code&gt; and run &lt;code&gt;chmod 600 forge_ai_keys.sml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;Keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;openrouter:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-or-v1-..."&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;forge_ai.sml&lt;/code&gt;, switch the provider:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;Ai&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;provider:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openrouter"&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;chatModels:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen/qwen3.8-27b:free, nvidia/nemotron-3-super-120b-a12b:free"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first model is used, and the others are fallbacks. Free models come and go, so check the current ones &lt;a href="https://openrouter.ai/models?max_price=0&amp;amp;supported_parameters=tools" rel="noopener noreferrer"&gt;with tool support&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send your next message. No restart needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Known rough edges
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;macOS build is Apple Silicon only and not notarized; Linux build is x86_64 only. No Windows build yet.&lt;/li&gt;
&lt;li&gt;ForgeCMS is the only output plugin so far.&lt;/li&gt;
&lt;li&gt;Free models are sometimes overloaded or change without notice.&lt;/li&gt;
&lt;li&gt;The UI still has a few leftovers from its origins as an SML editor.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Deploying a site straight from the app (Git push + SSH)&lt;/li&gt;
&lt;li&gt;More output plugins: HTML, Android, macOS apps, and &lt;strong&gt;books&lt;/strong&gt; (chapters by prompt, export to PDF/EPUB)&lt;/li&gt;
&lt;li&gt;Finer-grained "always allow" rules for commands like &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;ssh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is open source (GPLv3, with a commercial license available) on Codeberg: &lt;a href="https://codeberg.org/CrowdWare/Forge4D" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/Forge4D&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it, I'd love to hear what broke, what felt wrong, and what you'd build with it. Issues and comments welcome! 🌱&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>godot</category>
    </item>
    <item>
      <title>Sustainable Web Design with the Help of AI (Workshop)</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Thu, 17 Sep 2026 09:36:30 +0000</pubDate>
      <link>https://dev.to/artanidos/sustainable-web-design-with-the-help-of-ai-workshop-11bp</link>
      <guid>https://dev.to/artanidos/sustainable-web-design-with-the-help-of-ai-workshop-11bp</guid>
      <description>

&lt;h2&gt;
  
  
  Your website probably weighs more than it should
&lt;/h2&gt;

&lt;p&gt;Page builders with a hundred plugins. Subscription fees stacked on subscription fees. Hosting bills that make no sense for a five page site. Most of the modern web is built on foundations that waste energy, waste money and waste your time.&lt;/p&gt;

&lt;p&gt;I'm running a hands on workshop that takes the opposite approach: &lt;strong&gt;lean by default&lt;/strong&gt;. No database, no plugin jungle, no bloated hosting, and AI used as a real tool, not a crutch that writes code you don't understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll actually learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A practical approach to sustainable, low overhead web design&lt;/li&gt;
&lt;li&gt;How to use AI effectively in the design and build process, without losing control over the result&lt;/li&gt;
&lt;li&gt;The mindset behind &lt;a href="https://crowdware.info/forgecms" rel="noopener noreferrer"&gt;ForgeCMS&lt;/a&gt;: no database, no plugin jungle, no subscription trap&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;Freelancers, small teams, or anyone maintaining a website who is tired of the usual bloat and curious about a leaner, AI assisted alternative to conventional web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format and pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One day:&lt;/strong&gt; 4 hours, 130 EUR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekend:&lt;/strong&gt; 2 x 4 hours, 250 EUR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(plus venue rental)&lt;/p&gt;

&lt;h2&gt;
  
  
  Want in?
&lt;/h2&gt;

&lt;p&gt;Details and contact here: &lt;a href="https://crowdware.info/workshop" rel="noopener noreferrer"&gt;crowdware.info/workshop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No obligation, I reply personally, usually within a day.&lt;/p&gt;

&lt;p&gt;Built with love, coffee, and a stubborn focus on simplicity. That's the whole philosophy, and it's what you'll walk away with.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>cms</category>
    </item>
    <item>
      <title>Code &amp; Koje: Building Software with AI, Aboard a Sailboat in the Mediterranean</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Sun, 23 Aug 2026 21:53:19 +0000</pubDate>
      <link>https://dev.to/artanidos/code-koje-building-software-with-ai-aboard-a-sailboat-in-the-mediterranean-5eal</link>
      <guid>https://dev.to/artanidos/code-koje-building-software-with-ai-aboard-a-sailboat-in-the-mediterranean-5eal</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwj3xrrw382i7wu0fe2d.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwj3xrrw382i7wu0fe2d.webp" alt="Code &amp;amp; Koje: Building Software with AI, Aboard a Sailboat in the Mediterranean" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
I'm Olaf, a freelance software architect with over four decades of IT experience, an open source developer at CrowdWare, and starting in September I'll be sailing my ketch Deep Blue toward the Mediterranean.&lt;/p&gt;

&lt;p&gt;And I'm bringing people along.&lt;/p&gt;

&lt;p&gt;(Koje is German for a ship's bunk, hence the name.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;A weekend on board, somewhere in a marina in the Mediterranean. No seminar room, no slide deck, just a real boat, real remote work vibes, and hands on work building software with AI as a tool.&lt;/p&gt;

&lt;p&gt;For 3 to 4 people: students, aspiring developers, or anyone already in software who wants to see what working with AI actually looks like instead of just reading about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;This first weekend is all about ForgeCMS: build your own website, no WordPress bullshit anymore. Later sessions will move into Forge4D, building actual apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hands on experience developing with AI assistance&lt;/li&gt;
&lt;li&gt;Insight into cross platform development and open source workflows&lt;/li&gt;
&lt;li&gt;An Android tablet (worth about 120 EUR) and a solar panel (worth about 80 EUR) to keep, so you're set up to keep working and learning afterward&lt;/li&gt;
&lt;li&gt;A weekend that feels like a change of scenery, not a course&lt;/li&gt;
&lt;li&gt;Conversations about remote work, self employment, and life outside the classic office job&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;A deposit of 200 to 250 EUR is due in advance, covering your Android tablet and solar panel. The course itself is around 250 EUR per person, on top of the deposit.&lt;/p&gt;

&lt;h2&gt;
  
  
  When and Where
&lt;/h2&gt;

&lt;p&gt;Departure is early September, first leg heading south. There's a good chance the first sessions could happen along the way, even before the boat finds its winter berth in the Mediterranean. Exact dates and locations to follow here and on my channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interested?
&lt;/h2&gt;

&lt;p&gt;Reach out if you want to join or just have questions. Happy to chat either way, whether it turns into a booking or not.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Code &amp;amp; Koje: where software development meets sailing romance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codeberg.org/CrowdWare" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
    <item>
      <title>ForgeIL Has Been Self-Hosting Since May. Nobody Found It</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Mon, 22 Jun 2026 23:53:38 +0000</pubDate>
      <link>https://dev.to/artanidos/forgeil-has-been-self-hosting-since-may-nobody-found-it-4772</link>
      <guid>https://dev.to/artanidos/forgeil-has-been-self-hosting-since-may-nobody-found-it-4772</guid>
      <description>&lt;h1&gt;
  
  
  ForgeIL Has Been Self-Hosting Since May. Nobody Found It Yet.
&lt;/h1&gt;

&lt;p&gt;It got quiet here for a month. Not because the project stalled, but because life moved.&lt;/p&gt;

&lt;p&gt;I moved aboard my ketch, Deep Blue, now anchored in Rostock. A new band started rehearsing at a spot near the Admiralbrücke in Kreuzberg, we even found a proper rehearsal room now. Friends, a new city for some of it, a different rhythm. Good kind of busy.&lt;/p&gt;

&lt;p&gt;But here is the thing I noticed once I surfaced again: ForgeIL has been sitting there, self-hosting, since the Sprint 4 post in May. 62 tests passing. The SMS compiler compiling itself to LLVM IR. And in a month, basically nobody picked it up.&lt;/p&gt;

&lt;p&gt;That is not a complaint. It is just true, and it bugs me a little. Not because I need the attention, but because the thing is actually useful and almost nobody knows it exists yet.&lt;/p&gt;

&lt;p&gt;So this post is not a new technical milestone. It is a reminder, and an invitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ForgeIL Actually Is
&lt;/h2&gt;

&lt;p&gt;ForgeIL is the language family behind the Forge ecosystem at CrowdWare. Two parts, one intention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SML&lt;/strong&gt; (Simple Markup Language) describes structure. UI layout, app shape, content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMS&lt;/strong&gt; (Simple Multiplatform Script) describes behavior. Logic, compiled ahead of time through LLVM to native code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No virtual machine. No garbage collector. Events resolve at compile time, not through a runtime dispatcher, which is part of why SMS event dispatch measured at 4.6 nanoseconds in earlier benchmarks, ahead of C++, C# and Kotlin Native, with only Kotlin JVM's JIT still ahead.&lt;/p&gt;

&lt;p&gt;And as of Sprint 4, the compiler compiles itself. Lexer, parser and LLVM IR emitter, all written in SMS, all proven self-hosting. No C++ toolchain required to keep developing SMS itself anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why That Matters More Than It Sounds
&lt;/h2&gt;

&lt;p&gt;A self-hosting compiler is a small thing to read about and a real threshold to cross. It means the language is no longer just describing other programs, it can fully describe and rebuild itself. That is the point where a language stops being a personal project and starts being able to outlive its author.&lt;/p&gt;

&lt;p&gt;This is built under CWUP, the workflow I use across the whole stack: Claude as architect, Codex as coder, Claude back for review. Each sprint specified, implemented, checked, before the next one starts. If you are curious how far AI-assisted development can responsibly go on something as precise as a compiler, this is a working example, not a pitch.&lt;/p&gt;

&lt;p&gt;And underneath all of it sits one constraint that never gets compromised: Ahimsa, do no harm to any living being. No telemetry by design, no dark patterns, no lock-in. Dual licensed, GPL3 for the community, commercial for those who need it, same model Qt has used for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Ask
&lt;/h2&gt;

&lt;p&gt;I am not looking for employees or a funding round. I am looking for people who read "self-hosting compiler that compiles itself through LLVM, in a language with zero-cost UI events" and feel a small pull toward it.&lt;/p&gt;

&lt;p&gt;If that is you, here is where to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code lives at &lt;a href="https://codeberg.org/CrowdWare" rel="noopener noreferrer"&gt;codeberg.org/CrowdWare&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Look at the ForgeIL repo for the language docs and the self-hosting compiler source&lt;/li&gt;
&lt;li&gt;Try compiling something small, break something, open an issue, ask a question, anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not need permission to poke around in open source. You need a reason to. Maybe this is it.&lt;/p&gt;

&lt;p&gt;Aho 🌱&lt;/p&gt;




&lt;p&gt;&lt;em&gt;ForgeIL is the language layer of Forge 4D, a cross-platform app framework built around SML and SMS, native compiled, targeting Android ARM today with more platforms ahead. Dual licensed GPL3/commercial. Everything developed in the open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>compilers</category>
      <category>llvm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why SML/SMS doesn't run on Windows - and why that's a feature</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Mon, 11 May 2026 17:31:46 +0000</pubDate>
      <link>https://dev.to/artanidos/why-smlsms-doesnt-run-on-windows-and-why-thats-a-feature-3plf</link>
      <guid>https://dev.to/artanidos/why-smlsms-doesnt-run-on-windows-and-why-thats-a-feature-3plf</guid>
      <description>&lt;p&gt;Let me tell you a story.&lt;/p&gt;

&lt;p&gt;I was leading a raid in World of Warcraft. 24 people. Everyone waiting on me. And Windows decided that exact moment was the perfect time for an update. No warning. No mercy. Just a reboot.&lt;/p&gt;

&lt;p&gt;I switched to Mac the next day.&lt;/p&gt;

&lt;p&gt;For years it was great. Steve Jobs understood something most tech CEOs never will: the user is not the product. The user is the guest. You treat guests with respect.&lt;/p&gt;

&lt;p&gt;Then Steve left us.&lt;/p&gt;

&lt;p&gt;And slowly - update by update, policy by policy - Apple became just another corporation. A beautiful one. But one that now decides what software you can run, what hardware you can repair, and what belongs to you and what belongs to them.&lt;/p&gt;

&lt;p&gt;So here we are.&lt;/p&gt;

&lt;p&gt;We are building SMS - a programming language that compiles to native binaries via LLVM. Open source. GPL-3. &lt;strong&gt;No telemetry. No stakeholders. No investors.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It runs on Mac and Linux. Not on Windows - and that is not an oversight. That is a choice.&lt;/p&gt;

&lt;p&gt;Because we are not building for a world where your OS reboots your raid.&lt;br&gt;
We are not building for a world where a corporation decides what runs on your machine.&lt;br&gt;
&lt;strong&gt;We are building for the world we actually want to live in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ahimsa. Do no harm. Not even to your users.&lt;/p&gt;

&lt;p&gt;If you are on Windows and want to try SMS - WSL2 works fine. One small rebellion inside the machine.&lt;/p&gt;

&lt;p&gt;But if you are tired of building software on platforms that belong to someone else - come build with us.&lt;/p&gt;

&lt;p&gt;SMS is free. Linux is free. Your time is not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codeberg.org/CrowdWare/sms-cpp" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare/sms-cpp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
      <category>linux</category>
      <category>showdev</category>
    </item>
    <item>
      <title>ForgeOS Dojo - learn AI-assisted development, build something that matters</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Mon, 11 May 2026 17:08:06 +0000</pubDate>
      <link>https://dev.to/artanidos/forgeos-dojo-learn-ai-assisted-development-build-something-that-matters-9fg</link>
      <guid>https://dev.to/artanidos/forgeos-dojo-learn-ai-assisted-development-build-something-that-matters-9fg</guid>
      <description>&lt;p&gt;&lt;em&gt;"No Stakeholders. No Investors. Just Users."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In 28 days we built a new programming language from scratch.&lt;br&gt;
Today it compiles itself.&lt;/p&gt;

&lt;p&gt;Now we want to go further - together.&lt;/p&gt;

&lt;p&gt;We are opening the ForgeOS Dojo: a learning community where you will master AI-assisted software architecture while building something real.&lt;/p&gt;

&lt;p&gt;Not a todo app. Not another CRUD API.&lt;/p&gt;

&lt;p&gt;An entire mobile OS. Free. No ads. No forced updates. No spyware.&lt;/p&gt;

&lt;p&gt;You will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to work with AI as your architect (Claude) and your coder (Codex)&lt;/li&gt;
&lt;li&gt;Sustainable software design - less code, more intention&lt;/li&gt;
&lt;li&gt;SMS - our scripting language that now compiles itself to native binaries&lt;/li&gt;
&lt;li&gt;How to ship real software that respects its users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we build together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ForgeOS - a mobile OS built on SMS and C++&lt;/li&gt;
&lt;li&gt;Ahimsa as the single constitutional principle: do no harm - not even to your users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No prior OS experience needed. Just curiosity and the will to build something that matters.&lt;/p&gt;

&lt;p&gt;If you are tired of building software for companies that spy on their users - this is your Dojo.&lt;/p&gt;

&lt;p&gt;Drop a comment or follow CrowdWare on Codeberg:&lt;br&gt;
&lt;a href="https://codeberg.org/CrowdWare" rel="noopener noreferrer"&gt;https://codeberg.org/CrowdWare&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
      <category>career</category>
      <category>showdev</category>
    </item>
    <item>
      <title>We Wrote a Compiler in the Language It Compiles. It Works.</title>
      <dc:creator>Art</dc:creator>
      <pubDate>Mon, 11 May 2026 16:49:00 +0000</pubDate>
      <link>https://dev.to/artanidos/we-wrote-a-compiler-in-the-language-it-compiles-it-works-46p</link>
      <guid>https://dev.to/artanidos/we-wrote-a-compiler-in-the-language-it-compiles-it-works-46p</guid>
      <description>&lt;p&gt;&lt;em&gt;Four sprints. One afternoon. 62 tests, all green.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ForgeIL is the compiler layer of Forge - an open source UI framework built around&lt;br&gt;
a radical simplicity: one language, one binary, no runtime installation.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;There is a test every language eventually faces. Not a benchmark. Not a syntax&lt;br&gt;
comparison. A question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can the language compile itself?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A self-hosted compiler is the closest thing software has to a proof of maturity.&lt;br&gt;
If the language can express a lexer, a parser, and a code generator - and the&lt;br&gt;
result of running that code is a working binary - then the language has crossed&lt;br&gt;
a threshold. It is no longer a demo. It is a tool.&lt;/p&gt;

&lt;p&gt;SMS is the scripting language at the heart of Forge. It is interpreted at&lt;br&gt;
development time and compiled to LLVM IR at release time. We decided to test&lt;br&gt;
whether SMS could implement its own compiler front-end: a lexer, a parser, and&lt;br&gt;
an LLVM IR emitter - all written in SMS itself.&lt;/p&gt;

&lt;p&gt;Four sprints. One afternoon. It works.&lt;/p&gt;


&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;The self-hosted compiler lives in the &lt;code&gt;forgeil/&lt;/code&gt; directory of&lt;br&gt;
&lt;a href="https://codeberg.org/CrowdWare/sms-cpp" rel="noopener noreferrer"&gt;sms-cpp&lt;/a&gt; - three SMS source files that&lt;br&gt;
together form a complete compilation pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS Source
    |
    v
tokenize(src)        sprint1-lexer.sms     -&amp;gt;  array of Token
    |
    v
parse(tokens)        sprint2-parser.sms    -&amp;gt;  AST of Node
    |
    v
codegen(ast)         sprint3-codegen.sms   -&amp;gt;  LLVM IR text
    |
    v
clang                                      -&amp;gt;  native binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single convenience function wraps it all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun compile(src) {
    return codegen(parse(tokenize(src)))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire public API. Give it SMS source text. Get back LLVM IR.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sprint 1: Lexer
&lt;/h2&gt;

&lt;p&gt;The lexer turned out to be the place where SMS's integer-character API became&lt;br&gt;
a design constraint rather than a limitation. &lt;code&gt;str.charAt(i)&lt;/code&gt; returns the&lt;br&gt;
integer char code of the character at position &lt;code&gt;i&lt;/code&gt;. So the entire lexer is&lt;br&gt;
built on integer comparisons - no regex, no character class library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun isAlpha(code) {
    return (code &amp;gt;= 65 &amp;amp;&amp;amp; code &amp;lt;= 90) || (code &amp;gt;= 97 &amp;amp;&amp;amp; code &amp;lt;= 122) || code == 95
}

fun isDigit(code) {
    return code &amp;gt;= 48 &amp;amp;&amp;amp; code &amp;lt;= 57
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two-character operators (&lt;code&gt;==&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;) are handled with&lt;br&gt;
a single lookahead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (ch == 61) {  // '='
    if (i + 1 &amp;lt; len &amp;amp;&amp;amp; src.charAt(i + 1) == 61) {
        tokens.add(Token("OP", "==", line))
        i = i + 2
    } else {
        tokens.add(Token("ASSIGN", "=", line))
        i = i + 1
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: &lt;code&gt;tokenize(src)&lt;/code&gt; returns an array of &lt;code&gt;Token(type, value, line)&lt;/code&gt; data&lt;br&gt;
class instances. 12 tests, all green on the first run.&lt;/p&gt;


&lt;h2&gt;
  
  
  Sprint 2: Parser
&lt;/h2&gt;

&lt;p&gt;A recursive descent parser, written in SMS, for SMS.&lt;/p&gt;

&lt;p&gt;The interesting constraint here: SMS does not allow forward declarations. Every&lt;br&gt;
function must be defined before it is called - except at the top level, where&lt;br&gt;
all definitions are registered before &lt;code&gt;main()&lt;/code&gt; runs. So mutual recursion between&lt;br&gt;
&lt;code&gt;parseExpr&lt;/code&gt; and &lt;code&gt;parseStmt&lt;/code&gt; works perfectly, because both are defined at the&lt;br&gt;
module level before any call site is reached.&lt;/p&gt;

&lt;p&gt;The precedence chain follows the standard pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parsePrimary -&amp;gt; parsePostfix -&amp;gt; parseUnary -&amp;gt; parseMul -&amp;gt;
parseAdd -&amp;gt; parseCompare -&amp;gt; parseEquality -&amp;gt; parseAnd -&amp;gt; parseOr -&amp;gt; parseExpr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One non-obvious detail: the parser cursor is a single-element array used as a&lt;br&gt;
mutable integer box. SMS arrays are passed by shared reference - field&lt;br&gt;
assignments inside helper functions are visible to the caller. This is how the&lt;br&gt;
cursor advances across the recursive descent without needing a global variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun curTok(cur, tokens) { return tokens[cur[0]] }
fun advance(cur)         { cur[0] = cur[0] + 1 }
fun consume(cur, tokens, expected) {
    var tok = curTok(cur, tokens)
    if (tok.type != expected) { ... }
    advance(cur)
    return tok
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;24 tests. All green.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sprint 3: LLVM IR Emitter
&lt;/h2&gt;

&lt;p&gt;The code generator takes an AST and produces a complete LLVM IR text string.&lt;br&gt;
Integer-only subset: every SMS value is &lt;code&gt;i64&lt;/code&gt;, variables use alloca (memory-form&lt;br&gt;
SSA), and &lt;code&gt;clang&lt;/code&gt;/&lt;code&gt;llc&lt;/code&gt; applies &lt;code&gt;mem2reg&lt;/code&gt; in the optimization pipeline.&lt;/p&gt;

&lt;p&gt;The context object accumulates emitted instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class Ctx(tempCnt, labelCnt, code, terminated, loops)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because SMS data class instances use shared references (&lt;code&gt;shared_ptr&lt;/code&gt; under the&lt;br&gt;
hood), every helper function that mutates &lt;code&gt;ctx.code&lt;/code&gt; or &lt;code&gt;ctx.tempCnt&lt;/code&gt; has its&lt;br&gt;
changes visible to every other function holding the same &lt;code&gt;ctx&lt;/code&gt;. This is the&lt;br&gt;
foundation of the entire emitter design.&lt;/p&gt;

&lt;p&gt;A comparison compiles to an &lt;code&gt;icmp&lt;/code&gt; followed by a &lt;code&gt;zext&lt;/code&gt; - because LLVM&lt;br&gt;
comparisons produce &lt;code&gt;i1&lt;/code&gt; and SMS uses &lt;code&gt;i64&lt;/code&gt; everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (op == "&amp;lt;") { emit(ctx, cmpReg $ " = icmp slt i64 " $ left $ ", " $ right) }
emit(ctx, reg $ " = zext i1 " $ cmpReg $ " to i64")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a function like &lt;code&gt;fun add(a, b) { return a + b }&lt;/code&gt;, the emitter produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="vg"&gt;@sms_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%_p_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%_p_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;entry:&lt;/span&gt;
    &lt;span class="nv"&gt;%a&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;alloca&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;
    &lt;span class="k"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%_p_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%a&lt;/span&gt;
    &lt;span class="nv"&gt;%b&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;alloca&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;
    &lt;span class="k"&gt;store&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%_p_b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%b&lt;/span&gt;
    &lt;span class="nv"&gt;%t0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%a&lt;/span&gt;
    &lt;span class="nv"&gt;%t1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%b&lt;/span&gt;
    &lt;span class="nv"&gt;%t2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt;  &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%t1&lt;/span&gt;
    &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%t2&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the source contains a &lt;code&gt;fun main()&lt;/code&gt;, a C-compatible entry point is appended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="k"&gt;define&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="vg"&gt;@main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;entry:&lt;/span&gt;
    &lt;span class="nv"&gt;%ret64&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="vg"&gt;@sms_main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nv"&gt;%ret32&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;trunc&lt;/span&gt; &lt;span class="kt"&gt;i64&lt;/span&gt; &lt;span class="nv"&gt;%ret64&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;
    &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="nv"&gt;%ret32&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;19 tests. All green.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sprint 4: End-to-End
&lt;/h2&gt;

&lt;p&gt;The final sprint is the proof. A new C API function -&lt;br&gt;
&lt;code&gt;sms_native_execute_string_result&lt;/code&gt; - captures the string value produced by the&lt;br&gt;
SMS interpreter, instead of the integer it previously returned. This lets the&lt;br&gt;
host application receive the generated IR text directly.&lt;/p&gt;

&lt;p&gt;The test then does exactly what you would do on the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Run the SMS compiler in the interpreter, capture the IR string&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_ir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load_all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"fun main() { return 42 }"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Write to a temp .ll file&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Run: clang -O0 -o /tmp/test_bin /tmp/test.ll&lt;/span&gt;
&lt;span class="c1"&gt;// 4. Run the binary, check exit code == 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 42. Not chosen at random. The compiler's first words are a nod to&lt;br&gt;
the only question that ever mattered.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;forgeil_sprint4_tests: all tests passed (7)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline holds for arithmetic, if/else branches, and while loops. The tests&lt;br&gt;
skip gracefully when &lt;code&gt;clang&lt;/code&gt; is not available, so they run cleanly in any CI&lt;br&gt;
environment.&lt;/p&gt;


&lt;h2&gt;
  
  
  What the Numbers Look Like
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sprint 1 - Lexer        12 tests  v
Sprint 2 - Parser       24 tests  v
Sprint 3 - Code gen     19 tests  v
Sprint 4 - Self-host     7 tests  v
---------------------------------
Total                   62 tests  all green
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What This Is Not
&lt;/h2&gt;

&lt;p&gt;SMS is not trying to replace LLVM's front-end infrastructure. The self-hosted&lt;br&gt;
compiler covers the integer-only subset of the language: functions, variables,&lt;br&gt;
if/else, while, break/continue, arithmetic, and comparisons. Strings, arrays,&lt;br&gt;
data classes, and the standard library are outside its current scope.&lt;/p&gt;

&lt;p&gt;The point is not feature completeness. The point is that the language has&lt;br&gt;
enough expressive power to reason about itself. That is a different claim - and&lt;br&gt;
a meaningful one.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Everything is open source under GPL-3.0 (with a commercial option):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;sms-cpp&lt;/strong&gt;: &lt;a href="https://codeberg.org/CrowdWare/sms-cpp" rel="noopener noreferrer"&gt;codeberg.org/CrowdWare/sms-cpp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Lexer: &lt;code&gt;forgeil/sprint1-lexer.sms&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parser: &lt;code&gt;forgeil/sprint2-parser.sms&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Code gen: &lt;code&gt;forgeil/sprint3-codegen.sms&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tests: &lt;code&gt;tests/forgeil_sprint{1..4}_tests.cpp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to try it:&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 https://codeberg.org/CrowdWare/sms-cpp.git
git clone https://codeberg.org/CrowdWare/sml-cpp.git
cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build &lt;span class="nt"&gt;-DBUILD_TESTING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="nt"&gt;-DSML_CPP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;../sml-cpp
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build
&lt;span class="nb"&gt;cd &lt;/span&gt;build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ctest &lt;span class="nt"&gt;-R&lt;/span&gt; forgeil &lt;span class="nt"&gt;--output-on-failure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;*Forge is being built in public at &lt;a href="https://crowdware.info" rel="noopener noreferrer"&gt;crowdware.info&lt;/a&gt;.&lt;br&gt;
SMS, ForgeIL, and the self-hosted compiler are part of a longer project:&lt;br&gt;
a UI framework that runs anywhere without asking anything of the user's machine.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>cpp</category>
      <category>compiling</category>
    </item>
  </channel>
</rss>
