<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://holtwick.de</id>
    <title>Dirk Holtwick</title>
    <updated>2026-09-04T09:30:42.470Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://holtwick.de/"/>
    <link rel="self" href="https://holtwick.de/atom.xml"/>
    <subtitle>Software developer for web, mobile and other platforms. Strong focus on privacy enhancement.</subtitle>
    <logo>https://holtwick.de/icon-1024.png</logo>
    <icon>https://holtwick.de/icon-1024.png</icon>
    <rights>Copyright (c) Dirk Holtwick</rights>
    <entry>
        <title type="html"><![CDATA[Selling software worldwide from Europe]]></title>
        <id>https://holtwick.de/blog/sell-from-europe</id>
        <link href="https://holtwick.de/blog/sell-from-europe"/>
        <updated>2026-09-02T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[VAT, payment providers and merchants of record: why selling software worldwide from Europe is so hard – and which providers are actually left.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>Once upon a time selling software worldwide was easy. You would have a <a rel="noopener" target="_blank" class="external" href="https://www.paypal.com/">PayPal</a> account, sell your stuff without VAT applied, and at the end of the year you told the tax office how much you had sold in total. Now it is almost undoable, at least from Europe.</p><h2 id="why-not-go-to-the-app-store%3F" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#why-not-go-to-the-app-store%3F">Why not go to the App Store?</a></h2><p>“But hey, there are the App Stores, they take all the work off your hands.” That is partly true, at least for billing and taxes, but it comes with other drawbacks: a review for every single app version; a lot is forbidden in the <a rel="noopener" target="_blank" class="external" href="https://www.apple.com/app-store/">App Store</a>, technically and in terms of content; you know nothing about your customers; the fees are between 15 % and 30 %; you do get paid out in Euro, but at <a rel="noopener" target="_blank" class="external" href="https://www.apple.com/">Apple’s</a> own rate on the day of payout rather than the day of sale; and Apple can throw you out at any time — then go and try to take on a trillion-dollar corporation.</p><h2 id="what-is-so-complicated-about-it%3F" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#what-is-so-complicated-about-it%3F">What is so complicated about it?</a></h2><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">Not tax advice</p><p>I am a software developer, not a tax advisor. Everything here is my own experience from selling software, not tax or legal advice. The rules keep changing and depend on your particular situation — for anything binding, please ask your tax advisor.</p></div><p>Selling things in your own country, in my case Germany, is comparatively manageable: you write an invoice with your country’s VAT and that is that.</p><p>But as soon as you sell to other EU countries, you have to apply (and know) the standard tax rate of the destination country for software sales. If your customer is a company, you can ask for their VAT identification number (VAT ID) and issue a “reverse charge” invoice, so without tax, and they take care of it. But if the VAT ID turns out to be wrong, you owe that amount to your tax office after all. Worldwide, every country then has its own rules again, and in some of them you have to remit the VAT directly in the destination country — that depends on minimum turnovers which can range from 0 to well into the hundreds of thousands.</p><p>In Europe there are fortunately exemptions — below 10,000 Euro of annual turnover in other EU countries you may simply charge your home tax rate — and otherwise the <a rel="noopener" target="_blank" class="external" href="https://vat-one-stop-shop.ec.europa.eu/">One-Stop-Shop (OSS)</a> procedure. That at least saves you from reporting the taxes separately in every EU country, but you still have to know which taxes to charge. And that is not even uniform within a single country: Heligoland belongs to Germany, but for VAT purposes it counts as third territory (§ 1 (2) of the German VAT Act) — no German VAT applies there. The same goes for Büsingen am Hochrhein, the German enclave in the middle of Switzerland. Other EU countries have their own special zones. That is why the postal code is asked for everywhere. File it under useless knowledge ;)</p><h2 id="how-do-i-get-my-money%3F" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#how-do-i-get-my-money%3F">How do I get my money?</a></h2><p>Now we know how the invoice data has to look and how the taxes work. But we still have to get at the money as well. No problem, we will just use PayPal. But not everyone wants to or can use PayPal, and when PayPal gets into a really bad mood because a customer complained, they simply might close your account.</p><p>So let us take a payment provider — and there are fewer of them than you might think. The vast majority use <a rel="noopener" target="_blank" class="external" href="https://stripe.com/">Stripe</a>, they are in on pretty much everything. To be fair: for a European merchant this runs through Stripe’s Irish entity and pays out in Euro. But as soon as a customer pays in a different currency it gets <a rel="noopener" target="_blank" class="external" href="https://docs.stripe.com/currencies">converted</a>, and that costs around 2 %. And that is where the actual problem sits: we live here with the Euro, yet we fall back on providers whose internal reference currency is the dollar. Every station that converts takes a cut — and you are never the one setting the rate.</p><p>So we could pick a European payment provider like <a rel="noopener" target="_blank" class="external" href="https://www.mollie.com/">Mollie</a>. But they really do nothing other than accept the money and charge fees for it. And when a customer reverses a payment without giving you the chance to make a refund, some payment methods will cost you more than 10 EUR right away — you end up paying on top.</p><h2 id="but-surely-there-is-an-easier-way%3F-merchant-of-record" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#but-surely-there-is-an-easier-way%3F-merchant-of-record">But surely there is an easier way? Merchant of Record</a></h2><p>You can save yourself a lot of that by using a so-called merchant of record (MoR). They then sell your software in their own name and on their own account. They take care of all tax matters and once a month you get the money transferred, minus the taxes and their fees.</p><p>I have been using <a rel="noopener" target="_blank" class="external" href="https://www.paddle.com/">Paddle</a> from London for years and it really works quite well. You can even run everything in Euro, which avoids some losses. Their support is not particularly fast, but it works. You know all your customers and their data. But a MoR has rules too and does not sell everything. And you also have to trust that it will keep working.</p><p>Recently, though, the website for one of my products had to go through a second review for content reasons. That got sorted out and Paddle resolved it promptly — still, I took it as a reason to have a look around the MoR market. Just as a plan B for the worst case. And there are not nearly as many that really fit…</p><p>There are more MoRs than the ones I name here — what I was after is the pattern that runs through almost all of them: they are not based in the EU. <a rel="noopener" target="_blank" class="external" href="https://www.lemonsqueezy.com/">Lemon Squeezy</a> belongs to Stripe by now and is <a rel="noopener" target="_blank" class="external" href="https://www.lemonsqueezy.com/blog/2026-update">visibly drifting</a> towards Stripe Managed Payments — I would not start there any more. <a rel="noopener" target="_blank" class="external" href="https://fastspring.com/">FastSpring</a> has been around since the early days and still looks like it. Both USA. And even the fresh Swiss startup <a rel="noopener" target="_blank" class="external" href="https://tiun.io/">tiun</a> uses Stripe, so you end up half in the USA again.</p><p>A MoR from the EU would be good then. They do exist, just in two flavours: young and unfinished, or old and cut for corporations. <a rel="noopener" target="_blank" class="external" href="https://www.creem.io/">Creem</a> from Estonia only offers a small range of payment methods, not even PayPal, which roughly half of my buyers use. <a rel="noopener" target="_blank" class="external" href="https://www.cleverbridge.com/">cleverbridge</a> from Cologne and <a rel="noopener" target="_blank" class="external" href="https://nexway.com/">Nexway</a> from France, on the other hand, have been doing this for over twenty years — but with an annual fee, pricing on request and a sales call instead of a signup form. That is nothing for a single developer. That leaves the hope of <a rel="noopener" target="_blank" class="external" href="https://vatly.com/">Vatly</a> from the Netherlands with its Europe-first approach — so far only a waiting list, though.</p><p>In any case, what is left for small indie software vendors is pretty thin. You keep running into the same companies here: the alternative distribution channel <a rel="noopener" target="_blank" class="external" href="https://setapp.com/">Setapp</a>, for instance, is intertwined with Paddle, and Stripe is everywhere too and even wanted to <a rel="noopener" target="_blank" class="external" href="https://www.axios.com/2026/08/28/stripe-advent-end-paypal-pursuit">buy PayPal</a> — over 50 billion dollars together with the investor Advent, PayPal’s board found it too little, and by the end of August 2026 the plans were off the table again.</p><h2 id="wishes" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#wishes">Wishes</a></h2><p>I have not even talked about legal aspects, e-invoicing and different currencies. It would be desirable if all of this were much simpler and more carefree. If the EU, in the interest of its citizens, would create the foundations for sole traders to sell software worldwide very easily, without having to dig deeply into it or depend on resellers, because doing it yourself is barely realistic. Then the world could move a little closer together again.</p><h2 id="p.s." tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/sell-from-europe#p.s.">P.S.</a></h2><p>Of course the topic got to me and I went and programmed my own little shop, with a <a rel="noopener" target="_blank" class="external" href="https://buy.holtwick.de/buy/bx-license">checkout dialog</a> and everything I had learned. Whether I will actually use it, I do not know yet…</p><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">Let&apos;s join forces</p><p>If you are dealing with this topic as well, I would be very glad to <a target="_blank" rel="noopener noreferrer" class="external" href="mailto:support@holtwick.de?subject=Selling%20Software%20Feedback">hear from you</a> or a discussion on <a rel="noopener" target="_blank" class="external" href="https://news.ycombinator.com/item?id=49540833#49553926">Hacker News</a>.</p></div><p class="img-wrapper"><a rel="noopener" target="_blank" class="external" href="https://buy.holtwick.de/buy/bx-license"><img alt width="1228" height="762" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/jglj0lndc30bwd.png"></a></p></div><p><em>Published on September 2, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-09-02T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Receipts Space 3.6]]></title>
        <id>https://holtwick.de/blog/receipts3-6</id>
        <link href="https://holtwick.de/blog/receipts3-6"/>
        <updated>2026-06-15T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[An update with improvements to everyday workflows. Here’s a quick overview of the key changes.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>An update with improvements to everyday workflows. Here’s a quick overview of the key changes.</p><h2 id="smarter-category-settings" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3-6#smarter-category-settings">Smarter Category Settings</a></h2><p>For recognized contacts, you can now define how a category is made: from the most frequently used category, from the category of the most recent receipt, or not at all. In addition, each contact can be assigned a fixed default category that always takes precedence.</p><h2 id="re-run-recognition" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3-6#re-run-recognition">Re-run Recognition</a></h2><p>Two new commands re-apply recognition to the selected receipts: either the searchable text layer is rebuilt, or amounts, date and vendor are re-read – just like a fresh import.</p><h2 id="automatic-recovery-for-damaged-libraries" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3-6#automatic-recovery-for-damaged-libraries">Automatic Recovery for Damaged Libraries</a></h2><p>If a library’s info file is missing or damaged, it is restored from its backup. Otherwise, the app offers a repaired copy that opens normally again – optionally including orphaned files, which are brought back in as new documents.</p><h2 id="%E2%80%9Csave-as%E2%80%9D-via-the-option-key" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3-6#%E2%80%9Csave-as%E2%80%9D-via-the-option-key">“Save As” via the Option Key</a></h2><p>Holding the Option key turns “Open PDF Document” and “Open Original” in the document list into “Save As.” This lets you quickly store a copy in another location.</p><h2 id="screenshot-in-the-feedback-form" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3-6#screenshot-in-the-feedback-form">Screenshot in the Feedback Form</a></h2><p>The feedback form has a new “Screenshot” button that captures part of your screen and attaches it to your message with a single click.</p><p>All details are available at <a rel="noopener" target="_blank" class="external" href="https://receipts-app.com/">receipts-app.com</a>.</p></div><p><em>Published on June 15, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-06-15T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Voilà PDFify 5.0 — sharper OCR, smarter compression, bonjour French]]></title>
        <id>https://holtwick.de/blog/pdfify5</id>
        <link href="https://holtwick.de/blog/pdfify5"/>
        <updated>2026-05-21T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[PDFify 5.0 brings Tesseract 5 OCR optimized for Apple Silicon, automatic language detection via Apple Vision, four new compression modes, and full French localization.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p><strong>Tesseract 5 OCR, optimized for Apple Silicon.</strong> We’ve upgraded to the latest Tesseract engine and tuned it specifically for M-series Macs. The result is faster recognition und better accuracy.</p><p><strong>Apple Vision OCR with automatic language detection.</strong> No more picking a language before you scan. PDFify figures out what it’s looking at and gets to work — whether it’s a German contract, a Chinese manual, or an English research paper.</p><p><strong>Compression, redesigned around four clear modes.</strong> Get full control over the trade-off between quality and size: compress only embedded images and keep real text sharp and searchable — or rasterize entire pages in color, grayscale, or black &amp; white for maximum size reduction. One decision, predictable result.</p><p><strong>Full French localization.</strong> Every menu, every label, every dialog — bonjour&nbsp;🇫🇷</p><p><a class="oui-button external" target="_blank" rel="noopener noreferrer" href="https://pdfify.app/?ref=holtwick">Update PDFify today and give it a spin …</a></p></div><p><em>Published on May 21, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-05-21T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Me, the AI, and Our Sandbox]]></title>
        <id>https://holtwick.de/blog/bx-review</id>
        <link href="https://holtwick.de/blog/bx-review"/>
        <updated>2026-04-16T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[A hands-on report about using the bx sandbox for macOS, with a special focus on AI and VSCode.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">BX is now available as a Mac app</p><p></p><p>The idea described here has grown into an app of its own: <strong><a rel="noopener" target="_blank" class="external" href="https://bx-ai.eu/">BX</a></strong> launches Claude, Claude Code, Cursor and other AI apps in a sandbox — and shows you which folders they may read and write. Everything about it at <a rel="noopener" target="_blank" class="external" href="https://bx-ai.eu/">bx-ai.eu</a>.</p></div><a class="app-codeberg-badge" target="_blank" rel="noopener" href="https://codeberg.org/holtwick/bx-mac"><span class="app-codeberg-badge-left"> Codeberg </span><span class="app-codeberg-badge-right">holtwick/bx-mac</span></a> <a class="app-github-badge" target="_blank" rel="noopener" href="https://github.com/holtwick/bx-mac"><span class="app-github-badge-left"> GitHub </span><span class="app-github-badge-right">holtwick/bx-mac</span></a><p>Three weeks ago, I <a href="https://holtwick.de/blog/bx-sandbox">published a sandbox</a> to keep the AI somewhat in check. The built-in safeguards that Claude Code offers, for example, seemed untrustworthy to me, since they could usually be overridden by my instructions to the app. On top of that, I had a very uneasy feeling because sensitive information like SSH keys or project-specific confidential data in <code>.env.local</code> wasn’t inherently protected from the tools’ access.</p><p class="img-wrapper"><img alt width="828" height="520" fetchpriority="high" decoding="async" data-zoom class="img-zoom" data-zoom-width="1656" data-zoom-height="1040" src="https://holtwick.de/assets/lchg45gdf5lyd6.png"></p><p>When I looked around at the available options, most of them seemed quite complex or restricted my workflow — for example, working inside a Docker environment. I had something in mind that would be as simple as using <code>.gitignore</code>. So I got straight to work — ironically with the AI — building a cage for it.</p><p>We decided to use Apple’s macOS sandbox functionality. In principle, the restrictions I had specified in <code>.bxignore</code> simply needed to be translated into sandbox instructions. One feature that was important to me was the ability to also protect my development environment, since sensitive operations usually happen within the IDE — in my case, Visual Studio Code.</p><p>It quickly became clear that the ignore file alone wasn’t enough. I added a configuration that could accommodate the specifics of each application being used. This also allowed me to grant access to multiple directories, so I could work on several projects simultaneously. For this reason, I introduced a <code>~/.bxconfig.toml</code>.</p><p>I can now type <code>bx projects</code> to launch my development environment. Visual Studio Code opens with access to my projects. Within those projects, all sensitive data is protected by <code>.bxignore</code>. On top of that, a global baseline configuration protects areas like <code>.ssh</code> and other sensitive locations. Neither the IDE nor the tools invoked within it — such as Claude Code — can access this data.</p><p>After a while, I decided to use Visual Studio Code exclusively for AI work and installed the more open <a rel="noopener" target="_blank" class="external" href="https://vscodium.com/">VSCodium</a> alongside it for everything I didn’t want to do with AI, because it’s really hard to fully “clean up” VSCode.</p><p>Of course, none of this provides 100% security, but it doesn’t have to. It offers enough protection to not feel completely exposed, and it barely restricts the usual workflows. For me personally, it’s a great fit, and I’d be happy if others found it useful too.</p><p>Give it a try:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-sh"><span class="line"><span style="color:#6f42c1">brew</span><span style="color:#032f62"> install</span><span style="color:#032f62"> holtwick/tap/bx</span><span style="color:#24292e"> </span></span></code></pre><hr><a class="app-codeberg-badge" target="_blank" rel="noopener" href="https://codeberg.org/holtwick/bx-mac"><span class="app-codeberg-badge-left"> Codeberg </span><span class="app-codeberg-badge-right">holtwick/bx-mac</span></a> <a class="app-github-badge" target="_blank" rel="noopener" href="https://github.com/holtwick/bx-mac"><span class="app-github-badge-left"> GitHub </span><span class="app-github-badge-right">holtwick/bx-mac</span></a></div><p><em>Published on April 16, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-04-16T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI Coding Tools in a Sandbox: Why Your File System Needs Protection]]></title>
        <id>https://holtwick.de/blog/bx-sandbox</id>
        <link href="https://holtwick.de/blog/bx-sandbox"/>
        <updated>2026-03-29T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[AI coding tools like Claude Code or Copilot have full access to your file system. bx protects everything except the current project directory with a single command.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p class="img-wrapper"><img alt="Photo by Kanhaiya Sharma on Unsplash" width="1684" height="575" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/0nwy5x8g1772fo0.jpg"></p><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">BX is now available as a Mac app</p><p></p><p>The CLI tool described here has grown into an app of its own: <strong><a rel="noopener" target="_blank" class="external" href="https://bx-ai.eu/">BX</a></strong> launches Claude, Claude Code, Cursor and other AI apps in a sandbox — and shows you which folders they may read and write. Everything about it at <a rel="noopener" target="_blank" class="external" href="https://bx-ai.eu/">bx-ai.eu</a>.</p></div><a class="app-codeberg-badge" target="_blank" rel="noopener" href="https://codeberg.org/holtwick/bx-mac"><span class="app-codeberg-badge-left"> Codeberg </span><span class="app-codeberg-badge-right">holtwick/bx-mac</span></a> <a class="app-github-badge" target="_blank" rel="noopener" href="https://github.com/holtwick/bx-mac"><span class="app-github-badge-left"> GitHub </span><span class="app-github-badge-right">holtwick/bx-mac</span></a><p>If you work with AI-powered coding tools today — be it Claude Code, Copilot, or Cursor — you quickly get used to the productivity boost. What’s easy to overlook: these tools have full access to your entire file system. Every command the AI executes runs with the same permissions as you.</p><p>That might be fine for a hobby project. But if you’re handling client data, have license keys on disk, SSH keys for production servers, or simply private photos in your home directory — at some point, you start feeling uneasy.</p><h2 id="the-existing-options" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#the-existing-options">The Existing Options</a></h2><p>Of course, there are ways to protect yourself:</p><p><strong>Docker containers</strong> offer good isolation, but they’re cumbersome for typical macOS development workflows. You need to mount volumes, configure IDE integration, and the workflow never feels quite native.</p><p><strong>Claude’s built-in restrictions</strong> — the tool politely asks before modifying files. But “asking” isn’t protection. A hallucinated file path, a misinterpreted command, and suddenly the AI reads something it shouldn’t have seen. The trust is based on the model’s behavior, not on a technical barrier.</p><p><strong>VSCode Workspace Trust</strong> protects against automatic code execution, but not against file system access from a terminal or an extension.</p><p>None of these solutions really convinced me.</p><h2 id="the-idea%3A-use-the-macos-sandbox" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#the-idea%3A-use-the-macos-sandbox">The Idea: Use the macOS Sandbox</a></h2><p>macOS ships with <code>sandbox-exec</code>, a kernel-level sandbox that Apple itself uses for App Store apps. The idea: a tool that launches any application so it can only see the current project directory — and nothing else.</p><p>The result is <strong>bx</strong>. A CLI tool you put in front of your actual command:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-bash"><span class="line"><span style="color:#6f42c1">bx</span><span style="color:#032f62"> claude</span><span style="color:#032f62"> ~/work/my-project</span></span></code></pre><p>That’s it. Claude Code starts with full access to <code>~/work/my-project</code> — but <code>~/Documents</code>, <code>~/Desktop</code>, <code>~/.ssh</code>, other projects, and everything else in the home directory is invisible.</p><h2 id="built-in-two-days-%E2%80%94-with-claude-code-itself" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#built-in-two-days-%E2%80%94-with-claude-code-itself">Built in Two Days — with Claude Code Itself</a></h2><p>The ironic part: bx was built entirely with the tool it’s designed to protect against. In two intensive days, Claude Code wrote the bulk of the code — from sandbox profile generation to CLI argument parsing to app discovery via macOS Spotlight.</p><p>It worked surprisingly well. Claude knew the (undocumented!) Apple Sandbox Profile Language and correctly handled its quirks — for instance, the fact that <code>deny</code> rules in SBPL always take precedence over <code>allow</code> rules, regardless of order. This means you can’t simply lock down the entire home directory and then add exceptions. bx solves this by scanning the home directory and selectively blocking only sibling directories.</p><h2 id="more-than-just-claude-code" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#more-than-just-claude-code">More Than Just Claude Code</a></h2><p>bx isn’t limited to one tool. It supports VSCode, Xcode, Terminal, and arbitrary commands out of the box. Any app can be added via a TOML configuration file:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-toml"><span class="line"><span style="color:#24292e">[</span><span style="color:#6f42c1">apps</span><span style="color:#24292e">.</span><span style="color:#6f42c1">cursor</span><span style="color:#24292e">]</span></span>
<span class="line"><span style="color:#24292e">bundle = </span><span style="color:#032f62">&quot;com.todesktop.230313mzl4w4u92&quot;</span></span>
<span class="line"><span style="color:#24292e">binary = </span><span style="color:#032f62">&quot;Contents/MacOS/Cursor&quot;</span></span></code></pre><p>The tool finds apps automatically via their macOS bundle ID — no hardcoded paths needed. And if you regularly work with the same projects, you can configure default directories per app:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-toml"><span class="line"><span style="color:#24292e">[</span><span style="color:#6f42c1">apps</span><span style="color:#24292e">.</span><span style="color:#6f42c1">code</span><span style="color:#24292e">]</span></span>
<span class="line"><span style="color:#24292e">workdirs = [</span><span style="color:#032f62">&quot;~/work/project-a&quot;</span><span style="color:#24292e">, </span><span style="color:#032f62">&quot;~/work/shared-lib&quot;</span><span style="color:#24292e">]</span></span></code></pre><p>A simple <code>bx code</code> then opens VSCode with exactly those directories — sandboxed.</p><h2 id="fine-grained-control" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#fine-grained-control">Fine-Grained Control</a></h2><p>What makes bx especially practical: you can hide files even within an allowed project directory. A <code>.bxignore</code> file in the project works like <code>.gitignore</code>:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-text"><span class="line"><span>.env</span></span>
<span class="line"><span>.env.*</span></span>
<span class="line"><span>*.pem</span></span>
<span class="line"><span>secrets/</span></span></code></pre><p>This keeps environment variables and certificates invisible, even when the project directory itself is fully accessible.</p><p>With <code>bx --dry</code> you can preview exactly what will be protected — without launching anything. It gives you a clear picture of the actual isolation.</p><h2 id="the-growing-attack-surface" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#the-growing-attack-surface">The Growing Attack Surface</a></h2><p>What many people underestimate: modern AI coding tools are far more than chat interfaces. Claude Code, for example, can execute shell commands, create and delete files, and access external services via MCP servers (Model Context Protocol) — databases, APIs, cloud infrastructure. On top of that, there are skills and hooks that can trigger actions automatically.</p><p>All of this happens in the user’s context, with their full permissions. bx’s sandbox operates at the kernel level: whether it’s an <code>rm</code> command, an MCP tool, or an automated hook trying to access <code>~/Documents</code> — the operating system blocks the access before it even happens. This is fundamentally different from a software-level restriction that could be bypassed.</p><p>Important to note: within the allowed project directory, everything is permitted — that’s intentional, otherwise you couldn’t work. If you have sensitive files there, you can selectively exclude them via <code>.bxignore</code>.</p><h2 id="being-honest" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#being-honest">Being Honest</a></h2><p>bx is not a high-security vault. <code>sandbox-exec</code> is an undocumented Apple API that could change with any macOS update. There’s no network protection — API calls, git push, and npm install all work normally. And sandbox rules are generated once at launch; directories created afterwards are not automatically protected.</p><p>But as a pragmatic security layer for everyday development, it works remarkably well. It’s the difference between “the AI can theoretically read everything” and “the AI can only see this one project.”</p><p>That said, to be clear: bx was built with the best of intentions and to the best of my knowledge, but it comes with no guarantees. It’s not a replacement for a professional security solution, and it doesn’t absolve anyone from thinking for themselves. If you’re working with truly critical data, don’t rely blindly on any single tool — no matter how well it works. bx is an additional layer of protection, not a substitute for common sense.</p><h2 id="try-it-out" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/bx-sandbox#try-it-out">Try It Out</a></h2><p>bx can be installed via Homebrew or npm:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-bash"><span class="line"><span style="color:#6f42c1">brew</span><span style="color:#032f62"> install</span><span style="color:#032f62"> holtwick/tap/bx</span></span>
<span class="line"><span style="color:#6a737d"># or</span></span>
<span class="line"><span style="color:#6f42c1">npm</span><span style="color:#032f62"> install</span><span style="color:#005cc5"> -g</span><span style="color:#032f62"> bx-mac</span></span></code></pre><p>The code is open source: <a class="app-codeberg-badge" target="_blank" rel="noopener" href="https://codeberg.org/holtwick/bx-mac"><span class="app-codeberg-badge-left"> Codeberg </span><span class="app-codeberg-badge-right">holtwick/bx-mac</span></a> <a class="app-github-badge" target="_blank" rel="noopener" href="https://github.com/holtwick/bx-mac"><span class="app-github-badge-left"> GitHub </span><span class="app-github-badge-right">holtwick/bx-mac</span></a></p><p>I’d love to hear your feedback, feature requests, and of course stars. And if you have interesting use cases — let me know!</p><hr><p><em>This blog post was written by Claude (Anthropic’s AI) and reviewed and approved by me. Fitting, given the topic, I’d say.</em></p></div><p><em>Published on March 29, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-03-29T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[EU funding for open source software through a performance-based platform]]></title>
        <id>https://holtwick.de/blog/eu-opensource</id>
        <link href="https://holtwick.de/blog/eu-opensource"/>
        <updated>2026-02-02T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[Proposal for an EU platform that funds open-source projects using measurable criteria, benefiting developers, the EU, and society.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">Info</p><p>The EU is asking for <a rel="noopener" target="_blank" class="external" href="https://netzpolitik.org/2026/konsultation-eu-kommission-arbeitet-an-neuer-open-source-strategie/">feedback on its open source strategy</a>. Here is my suggestion. Please send your feedback via <a rel="noopener" target="_blank" class="external" href="https://mastodon.social/@holtwick">Mastodon</a>.</p></div><h2 id="initial-situation" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/eu-opensource#initial-situation">Initial situation</a></h2><p>As a software developer, I face a fundamental dilemma: developing high-quality open-source software—such as my end-to-end encrypted video conferencing solution <a rel="noopener" target="_blank" class="external" href="https://brie.fi/ng">Briefing</a>—requires a considerable investment of time and capital. Without sustainable funding, my only option is commercial exploitation, even though I would prefer to make the software completely free and continue to develop it intensively. This problem affects numerous developers in Europe.</p><h2 id="core-idea" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/eu-opensource#core-idea">Core idea</a></h2><p>The EU should establish a permanent funding system that promotes open source development through performance-based remuneration. Specifically, I propose an EU-operated platform (comparable to GitHub or Codeberg.org) that distributes funding based on measurable success criteria.</p><h2 id="how-it-works" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/eu-opensource#how-it-works">How it works</a></h2><p>The platform would evaluate the usefulness and quality of projects using objective metrics:</p><ul><li>Community engagement (ratings, stars)</li><li>Technical reach (integration into other projects, downloads)</li><li>Maintenance quality (responding to issues, fixing bugs)</li><li>Documentation and support</li></ul><p>Funding would be distributed proportionally, with upper limits per project/person to prevent abuse.</p><h2 id="benefits-for-all-involved" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/eu-opensource#benefits-for-all-involved">Benefits for all involved</a></h2><ul><li><em>For developers</em>: Direct link between performance and remuneration; opportunity to focus entirely on open source development</li><li><em>For the EU</em>: Building digital sovereignty; control over quality standards (e.g., EUPL licensing, developer verification); promotion of European innovation; reduction of dependence on non-European platforms</li><li><em>For society</em>: Access to secure, transparent software; strengthening of the European tech ecosystem</li></ul><h2 id="conclusion" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/eu-opensource#conclusion">Conclusion</a></h2><p>This initiative would create systematic incentives for the development of high-quality open source software while promoting European values such as transparency, data protection, and digital sovereignty.</p></div><p><em>Published on February 2, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-02-02T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Receipts Space 3.0]]></title>
        <id>https://holtwick.de/blog/receipts3</id>
        <link href="https://holtwick.de/blog/receipts3"/>
        <updated>2026-01-21T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[Receipts Space 3.0 brings audit compliance, end-to-end encryption, a new dashboard and many workflow improvements.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>Receipts Space 3.0 is here – a major update with lots of new features. Here’s a quick overview.</p><a target="_blank" href="https://video.holtwick.de/w/mAkY1dP2tnPp2FnYNZ1Swk">https://video.holtwick.de/w/mAkY1dP2tnPp2FnYNZ1Swk</a><h2 id="audit-compliance-(gobd)" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3#audit-compliance-(gobd)">Audit Compliance (GoBD)</a></h2><p>For anyone who needs to store receipts in compliance with regulations, there’s now a dedicated mode. Confirmed entries become read-only, and all changes are logged. A history timeline shows at a glance who changed what and when.</p><h2 id="end-to-end-encryption" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3#end-to-end-encryption">End-to-End Encryption</a></h2><p>If you store your library in the cloud or on a USB stick, you can now encrypt it with a password. Synchronization between multiple Macs still works – as long as all devices know the key.</p><h2 id="new-dashboard" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3#new-dashboard">New Dashboard</a></h2><p>The dashboard has been completely redesigned. In addition to income and expenses, it now shows open amounts and trends compared to the previous year. A click on contacts, categories or tags filters the view.</p><p class="img-wrapper"><img alt width="3488" height="2264" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/fnsa6jeb1470f9w.png"></p><h2 id="folder-instead-of-package" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/receipts3#folder-instead-of-package">Folder Instead of Package</a></h2><p>The file format has been extended with a new option: The library can now also be saved as a regular folder. This solves synchronization issues with iCloud and other services.</p><p>Of course, there are many more changes. All details are available at <a rel="noopener" target="_blank" class="external" href="https://receipts-app.com/en/blog/release-3">receipts-app.com</a>.</p></div><p><em>Published on January 21, 2026</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2026-01-21T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[UnplugTrump]]></title>
        <id>https://holtwick.de/blog/unplug-trump</id>
        <link href="https://holtwick.de/blog/unplug-trump"/>
        <updated>2025-03-11T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[UnplugTrump: How can you make yourself independent of US services, especially in the age of Donald Trump? The key points are self-hosting, decentralization and open source.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>What crazy times we live in, and now everything is becoming even more absurd with President Trump. Regardless of the big-picture political situation, the goal should now be to become independent of the US and strengthen the EU. There is also a lot that can be done in the private sphere, and I would like to gradually present alternatives for US services and products in a small series.</p><p>Early 2020, I had tried to sketch out in my article <a href="https://holtwick.de/blog/it-strategy-for-europe">“An IT Strategy for Europe”</a> what I thought such a step towards digital sovereignty might look like in broad strokes. Not much is happening in this regard.</p><p>I would like to offer the following criteria for achieving independence and reliability:</p><ul><li><strong>Self-hosting</strong> → If you operate your services yourself, no one can shut them down and the data belongs to you.</li><li><strong>Decentralized</strong> → A distributed system is less susceptible to outages and censorship.</li><li><strong>Open source</strong> → The software cannot disappear and can be verified.</li></ul><p><img alt width="775" height="434" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/l0xwi0281x0e78m.jpg"><em>Image by <a rel="noopener" target="_blank" class="external" href="https://chat.mistral.ai/chat/93944700-3a0c-4d90-a1a6-c42c5462064e">mistral.ai</a></em></p><h2 id="selfhosting" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#selfhosting">Selfhosting</a></h2><p>It is so nice and convenient to use the services of large providers like Google, Microsoft or Apple, but more often than not you pay with your personal data. Even services like Apple can quickly change their privacy-friendly stance; there are already signs of this and the history of comparable services makes trust shrink.</p><h3 id="home-network" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#home-network">Home network</a></h3><p>Unfortunately, operating your own server is not for everyone, but it is less complicated than you might think. You can start at home with a <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Network-attached_storage"><strong>NAS</strong></a>. I own a <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Synology">Synology</a> (Taiwan). It comes with many services of good quality that cover the essentials. But actually it can be any computer, the main thing is that <strong><a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Docker_(software)">Docker</a></strong> can be operated on it.</p><p><strong>Docker</strong> is the key to really easy and secure operation of “self-hosted services”. There is an almost <a rel="noopener" target="_blank" class="external" href="https://github.com/awesome-selfhosted/awesome-selfhosted">infinite number of solutions</a> for every problem.</p><p>For example, I run <a rel="noopener" target="_blank" class="external" href="https://www.home-assistant.io/"><strong>Home Assistant</strong></a> (open source) on my Synology, which is the dashboard for all devices and sensors in my own home that are somehow connected to the network. For photos, I use <a rel="noopener" target="_blank" class="external" href="https://immich.app/"><strong>Immich</strong></a> (open source), which also has features that can keep up. A <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Virtual_private_network">VPN connection</a> - in my case WireGuard via a fritz.box - allows me to use all services while on the road.</p><h3 id="internet" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#internet">Internet</a></h3><p>But even running a Docker service on the internet is not rocket science. At <a rel="noopener" target="_blank" class="external" href="https://hetzner.cloud/?ref=thK9VpOJK5Sg">Hetzner</a> (promo link), for example, you can set up a virtual server for less than 5 EUR and get it ready to go with Docker. After a few preparations (<a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/selfhosted?tab=readme-ov-file#selfhosted">https://github.com/holtwick/selfhosted?tab=readme-ov-file#selfhosted</a>) to connect your own domain to the IP, you’re ready to go.</p><p>I run many services there, but for private use I mainly use <a rel="noopener" target="_blank" class="external" href="https://nextcloud.com/"><strong>nextCloud</strong></a>. The look is a bit outdated in some places, but it serves its purpose for file sharing and other things you know from iCloud, for example.</p><h2 id="decentralized" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#decentralized">Decentralized</a></h2><p>Everyone uses the internet’s most famous decentralized service: email. Actually, the whole magic of the internet is its decentralized structure. If something fails somewhere, the information finds another way, just like an ant trail.</p><p>Large providers are usually centralized. Of course, they try to distribute the load in the background, but these services are still technically vulnerable to disruptions, data loss and security breaches.</p><p>All the better that there are now more “federated” services again. Lately, <a rel="noopener" target="_blank" class="external" href="https://joinmastodon.org/"><strong>Mastodon</strong></a> has become particularly well known as a Twitter /X alternative. But in this so-called <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Fediverse">Fediverse</a>, other solutions are also emerging that can then also connect with each other. A good example of this is the YouTube replacement <a rel="noopener" target="_blank" class="external" href="https://joinpeertube.org/de/"><strong>PeerTube</strong></a>.</p><p>But there are other forms of decentralized data processing. One movement that I particularly like is called <a rel="noopener" target="_blank" class="external" href="https://www.localfirstconf.com/"><strong>Local First</strong></a>, which again emphasizes the importance of data being available locally. Storing the same data in different physical locations is also a form of decentralized structure. The prime example of this is versioning systems like <a rel="noopener" target="_blank" class="external" href="https://git-scm.com/"><strong>git</strong></a>.</p><h2 id="open-source" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#open-source">Open Source</a></h2><p>Free and open software is the cornerstone of deeper trust. First, open software is not so quickly “out of the world”; it exists a thousand times over in copies. Secondly, with a little expertise, you can see exactly what the software does. And thirdly, it can be adapted, which in turn contributes to the project. It’s a digital form of volunteer work, without having to join an association ;)</p><h2 id="further-links" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/unplug-trump#further-links">Further links</a></h2><ul><li>Kuketz blog on the same topic with interesting links and tips (German):<br><a rel="noopener" target="_blank" class="external" href="https://www.kuketz-blog.de/unplugtrump-mach-dich-digital-unabhaengig-von-trump-und-big-tech/">https://www.kuketz-blog.de/unplugtrump-mach-dich-digital-unabhaengig-von-trump-und-big-tech/</a></li><li>Directory of European alternatives:<br><a rel="noopener" target="_blank" class="external" href="https://european-alternatives.eu/">https://european-alternatives.eu</a></li><li>General search for alternatives:<br><a rel="noopener" target="_blank" class="external" href="https://alternativeto.net/">https://alternativeto.net</a></li><li>Lots of pre-installed self-hosted projects, good for testing:<br><a rel="noopener" target="_blank" class="external" href="https://adminforge.de/">https://adminforge.de</a></li></ul></div><p><em>Published on March 11, 2025</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2025-03-11T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[E-Invoice Space]]></title>
        <id>https://holtwick.de/blog/einvoice-space</id>
        <link href="https://holtwick.de/blog/einvoice-space"/>
        <updated>2025-01-29T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[Transform your invoicing process with E-Invoice Space. Create, convert and manage EN 16931 compliant e-invoices with our powerful web application. Free PDF to XML conversion.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>After the successful launch of <strong>XML2Invoice</strong>, many new features and improvements have been added, so the renaming to <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/?ref=holtwick"><strong>E-Invoice Space</strong></a> is now taking place.</p><h2 id="new-layout" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/einvoice-space#new-layout">New layout</a></h2><p>The space is now used more sensibly, with only two areas: the PDF preview and the input area. It is also possible to adjust the size ratio between the two areas. There is now a general division between <strong>reading</strong> and <strong>writing</strong> to better highlight the strengths of <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/?ref=holtwick"><strong>E-Invoice Space</strong></a> and simplify access to the functions.</p><h2 id="create-your-own-invoices" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/einvoice-space#create-your-own-invoices">Create your own invoices</a></h2><p>In the <strong>Write</strong> area, in addition to enriching an existing simple PDF with e-invoice data, a completely new invoice can now be created. All the necessary data can be conveniently entered and a preview is generated immediately. In the PRO version, it is also possible to select an individual background design (stationery) and upload your own fonts in TTF format.</p><h2 id="en-16931-compatibility" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/einvoice-space#en-16931-compatibility">EN 16931 compatibility</a></h2><p><a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/?ref=holtwick"><strong>E-Invoice Space</strong></a> now also meets the requirements of the EN 16931 standard, not just BASIC and MINIMAL as before.</p><p>Overall, the application has become more comprehensive and user-friendly, enabling quick results. As usual, everything takes place locally, so that the web application behaves like a locally installed application. No data is stored or transferred to a cloud service.</p></div><p><em>Published on January 29, 2025</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2025-01-29T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Write your first e-invoice...]]></title>
        <id>https://holtwick.de/blog/einvoice</id>
        <link href="https://holtwick.de/blog/einvoice"/>
        <updated>2025-01-06T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[Convert PDF invoices to e-invoices instantly. Create EN 16931, XRechnung, and ZUGFeRD compliant electronic invoices online. Free tool for small businesses and freelancers.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p><img alt class="img-right" width="533" height="800" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/dwp75m1o10jr3lk.jpeg">…with our new <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/?ref=holtwick">Swiss army knife for e-invoices</a>. Following the latest update, e-invoices can now be easily created online. Even with your own stationery and fonts. The result is a PDF that is compatible with all required standards, including EN 16931, XRechnung, ZUGFeRD and PDF/A.</p><p>The highlight: invoices that were previously created using Word or similar programs can be converted into an e-invoice in no time at all. Simply upload the PDF of the existing invoice, the values are read out as if by magic and the result is a standard-compliant e-invoice in ZUGFeRD format. This means that the electronic data has been embedded in the PDF as XML and the other technical requirements have also been met.</p><p>The result can be quickly saved and sent. But also in our Mac tool <a rel="noopener" target="_blank" class="external" href="https://receipts-app.com/?ref=holtwick">Receipts</a>.</p><p>But that’s not all. Received e-invoices, whether XML or PDF, can simply be dragged into the XML2Invoice window and everything is readable and ready for payment or further processing.</p><p><em>As I said, the Swiss army knife of e-invoicing</em> 😎</p><p class="action"><a class="button oui-button external" target="_blank" rel="noopener noreferrer" href="https://e-invoice.space/?ref=holtwick">Write your first e-invoice now!</a></p><p><strong>Update 2025-01-29:</strong> XML2Invoice is now called <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/">E-Invoice Space</a></p></div><p><em>Published on January 6, 2025</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2025-01-06T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[XML2Invoice]]></title>
        <id>https://holtwick.de/blog/xml2invoice</id>
        <link href="https://holtwick.de/blog/xml2invoice"/>
        <updated>2024-10-30T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[E-invoices:Easily convert and pay. Basic PDF becomes machine readable, XML invoice becomes human-readable]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p><strong>Worried about e-invoices? With XML2invoice you can start the new year relaxed…</strong></p><p class="img-wrapper"><img alt width="2736" height="1824" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/ld44m5h1twywwl.jpeg"></p><p><a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/"><strong>XML2Invoice</strong></a> is a web-based app that converts your “normal” PDF invoice into XML format: Either into a pure <strong>XML file</strong> or into a <strong>ZUGFeRD</strong> PDF file that looks exactly like your invoice and in which the prescribed XML data is embedded.</p><p>And it also works the other way around: <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/"><strong>XML2Invoice</strong></a> generates an electronically readable invoice in <strong>ZUGFeRD format</strong> from XML files that can no longer be read by humans, which looks like a standard PDF invoice on the outside, but also contains the XML data. Readable for humans and machines. If desired, you can import the PDF with the embedded XML data <strong>into Receipts</strong> with a click of the mouse.</p><p class="action"><a class="button oui-button external" target="_blank" rel="noopener noreferrer" href="https://e-invoice.space/">Start now for free on xml2invoice.com !</a></p><p><strong>Update 2025-01-29:</strong> XML2Invoice is now called <a rel="noopener" target="_blank" class="external" href="https://e-invoice.space/">E-Invoice Space</a></p></div><p><em>Published on October 30, 2024</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2024-10-30T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[PDFify 4.0]]></title>
        <id>https://holtwick.de/blog/pdfify4</id>
        <link href="https://holtwick.de/blog/pdfify4"/>
        <updated>2024-09-12T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[Upgrade to PDFify 4.0 for PDF/A support, batch conversions, faster web display, and standardized page sizes. Try it now!]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p><a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a> can now be updated to the latest version 4.0. Many <a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a> users are looking forward to this major version update with improvements and new functions.</p><h2 id="pdf%2Fa-support" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/pdfify4#pdf%2Fa-support">PDF/A Support</a></h2><p>Many <a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a> users keep electronic documents that they need to archive for the long term.</p><p><a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/PDF/A">PDF/A</a> - Portable Document Format Archivable - enables standardized long-term archiving of documents. As of macOS 11, <a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a> now offers to save the document as PDF/A-2u by default in the settings.</p><p class="img-wrapper"><img alt width="592" height="537" fetchpriority="high" decoding="async" data-zoom class="img-zoom" data-zoom-width="1184" data-zoom-height="1074" src="https://holtwick.de/assets/dduxhx5g18q6yfe.png"></p><p>The “classic” PDF, which supports the embedding of links, graphics, audio and video files as well as optional encryption, is of course retained in <a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a>.</p><h2 id="finder-extensions-%2F-quick-actions" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/pdfify4#finder-extensions-%2F-quick-actions">Finder Extensions / Quick Actions</a></h2><p>To quickly convert several existing PDFs “in batch” to PDF/A, the quick actions in the Finder Extension can be supplemented with “Convert to PDF/A”.</p><p>There is also a new Finder extension for converting documents into a PDF with PDF/A format. However, the existing extensions also generate PDF/A if the option has been set in the settings.</p><div class="markdown-alert markdown-alert-tip"><p class="markdown-alert-title">Other file formats</p><p>Not only PDF can be optimized using the Finder Extensions, but also documents in various formats can be converted directly to PDF:</p><ul><li>Image files and scans such as JPG, PNG, TIF or GIF</li><li>HTML files</li><li>Emails</li></ul></div><h2 id="faster-display-of-pdfs-on-the-web" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/pdfify4#faster-display-of-pdfs-on-the-web">Faster display of PDFs on the web</a></h2><p>Furthermore, you can now also select the linearized PDF, which is optimized for fast display on the Internet. The first page is then already displayed during the download, while the following pages are still being loaded.</p><h2 id="standardized-maximum-page-size" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/pdfify4#standardized-maximum-page-size">Standardized maximum page size</a></h2><p><a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a> now allows you to limit the maximum page size so that you do not end up with unsightly huge pages. By default, the page size is set to DIN A4 with the update. In the settings, you can adjust the value to the original size or US letter.</p><p class="img-wrapper"><img alt width="592" height="537" loading="lazy" decoding="async" data-zoom class="img-zoom" data-zoom-width="1184" data-zoom-height="1074" src="https://holtwick.de/assets/gq7xpta81829c4t.png"></p><h2 id="sequence-during-import" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/pdfify4#sequence-during-import">Sequence during import</a></h2><p>Sometimes the individual pages of a document are available as separate image files. In the new version of <a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick">PDFify</a>, these are now reliably imported in the sort order familiar from the Apple Finder. This allows individual scans of pages to be quickly merged into one PDF document.</p><p><a rel="noopener" target="_blank" class="external" href="https://pdfify.app/?ref=holtwick"><strong>Try now for free</strong></a></p></div><p><em>Published on September 12, 2024</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2024-09-12T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Resilient Sync for Local First]]></title>
        <id>https://holtwick.de/blog/localfirst-resilient-sync</id>
        <link href="https://holtwick.de/blog/localfirst-resilient-sync"/>
        <updated>2024-06-24T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[Resilient sync for local-first apps by using existing technologies such as file systems]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><div class="markdown-alert markdown-alert-success"><p class="markdown-alert-title">Update 2025-02-11</p><p>With <a rel="noopener" target="_blank" class="external" href="https://receipts-app.com/?ref=holtwick">Receipts Space</a>, I have now published the first app based on the <a rel="noopener" target="_blank" class="external" href="https://receipts-app.com/docs?ref=holtwick">technology</a> described. It has proven itself and more will follow.</p></div><p>When I started programming in the 80s, the situation was simple: data was written to a file on a local disk. If files needed to be exchanged, a floppy disk was simply inserted into another computer and edited.</p><p>Later, floppy disks and CD drives disappeared and files were either exchanged by e-mail or the data was stored directly on a server. Even Microsoft Word moved to the cloud at some point.</p><p>However, moving data from the local computer to the cloud created new problems. What if the provider closed its service? However, it was not only data loss, but also being trapped by the provider because the data could no longer be retrieved from the service that increasingly became a problem for some and a business model for others.</p><h2 id="local-first" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#local-first">Local-First</a></h2><p>The <a rel="noopener" target="_blank" class="external" href="https://localfirstweb.dev/">local-first movement</a> wants to bring the data back to the user while retaining the advantages of the internet. A robust and widely accepted solution has been found for the simultaneous processing of data with <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDT</a>, but not yet for the exchange of data. This is because it is not (yet) possible to do completely without services on a server. Even with peer-to-peer, where the devices mainly communicate directly with each other, the connection must be mediated by servers at the beginning.</p><p>In an ideal local-first world, the following criteria should be met with regard to the exchange of data:</p><ol><li>data is processed and stored locally (offline).</li><li>the sync can be interrupted for a longer period of time, but data can be processed locally during this time and still be identical everywhere after a new sync (CRDT).</li><li>data should not be visible on the move (E2EE).</li></ol><p>And I would also like to add:</p><ol start="4"><li>the whole thing should also work with the technology of the 80s.</li></ol><p>Why? For reasons of resilience, because there are many reasons why things could go offline.</p><h2 id="resilient-sync" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#resilient-sync">Resilient Sync</a></h2><p>For this reason, I propose a data exchange format that works both in the file system and on the Internet. It should be so simple that the implementation can work on any underlying data carrier or service.</p><h3 id="log-of-changes" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#log-of-changes">Log of changes</a></h3><p>To start with, each client writes a kind of simple, continuous log with the changes. We call the position of the changes <code>index</code> and it starts at <code>0</code> and continues in whole numbers: <code>0, 1, 2, 3, ...</code>. Usually these changes are CRDT compliant, but this is not significant for the protocol. This data can also be stored in encrypted form. Let’s call this <code>data</code>.</p><p>Each client is given a unique identifier, usually a unique ID (UUID), we call it <code>clientId</code>. The change logs are stored linked to the <code>clientId</code>. We will call a collection of such logs <code>workspace</code>.</p><p>We can enrich further data, such as a <code>timestamp</code>, which can be useful if we want to process the data changes historically or implement a kind of endless “undo”.</p><p>It is also conceivable that each new entry contains a hash of the previous entry in order to ensure data consistency in the style of a blockchain. Further refinements in the direction of the Merkle tree are also conceivable. However, a hash on the content of the current entry can also contribute to data security.</p><h3 id="assets%2C-blobs%2C-the-big-binary-chunks" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#assets%2C-blobs%2C-the-big-binary-chunks">Assets, blobs, the big binary chunks</a></h3><p>The reality is that there is also larger data that rarely changes: Images, videos, audios, i.e. files. These are not part of the content changes and would quickly bring data synchronization to a standstill. In addition, they are not always needed immediately and could also be loaded “on demand”. I therefore suggest treating these files separately from the changes. Let’s call them <code>asset</code>.</p><p>But here, too, the data should be stored according to <code>clientId</code> and in ascending order with <code>index</code>. The data sets can thus refer to an asset, e.g. with a data entry in the form of a URL that contains all the necessary information:<br><code>asset:///&lt;clientId&gt;/&lt;index&gt;/&lt;filename&gt;?size=&lt;sizeInBytes&gt;&amp;type=&lt;mimeType&gt;&amp;hash=&lt;checksumOfContents&gt;</code>. An example could look like this <code>asset:///abc/1/test.txt?size=100&amp;type=text%2Fplain&amp;hash=1a2b3c</code>.</p><h3 id="benefits" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#benefits">Benefits</a></h3><p>The key advantage of this method is that we always know where the next data will appear. Because if a client has just written the index ‘123’, the next one will be ‘124’.</p><p>Why is this important? For the following reasons:</p><ol><li>we are not dependent on being notified of new data (“push”), but can also ask for it ourselves (“pull”).</li><li>we can load the individual entries even without knowing anything about their content. A sync can even take place without an intermediary having to “understand” the data.</li><li>we notice immediately if data is missing and can request it again.</li><li>the data can be replicated as often as required, there does not have to be just one sync source. It therefore makes sense to use it as a backup.</li><li>clients that do not have a direct connection to each other can use “stupid copying processes”.</li><li>data exchange can be easily and comprehensibly documented, which can be important when recognizing legally compliant stored data for tax and compliance purposes.</li><li>there are no conflicts when storing the data because it is only updated but never deleted (“append only”).</li></ol><h3 id="databases" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#databases">Databases</a></h3><p>Let’s start with the implementation as a database. As described, a table with the following fields would be sufficient:</p><ul><li><code>index</code>: Integer</li><li><code>clientId</code>: String or integer</li><li><code>data</code>: String or binary</li><li><code>timestamp</code>: Integer (optional)</li><li><code>prevHash</code>: String or integer (optional)</li></ul><p>This would look similar for the assets.</p><p>Database can refer to both the local storage of the client, e.g. in the IndexedDB, and the storage on a synchronization server.</p><h3 id="filesystems" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#filesystems">Filesystems</a></h3><p>The data is stored in a directory in a file system. Metadata, such as the <code>clientId</code> of the creator and details such as the encryption used, are stored in a JSON file called <code>index.json</code>.<br>Furthermore, there is a directory for each client that is named after the <code>clientId</code>.</p><p>These in turn each contain two directories:</p><ol><li><code>changes</code></li><li><code>assets</code></li></ol><p>The changes are recorded in <code>changes</code> and numbered consecutively. The same is done in <code>assets</code> with the binary data described.</p><p>Modern file systems have no significant restrictions on the number of files per directory, but it can’t hurt to limit the number anyway. The following algorithm in TypeScript ensures an even distribution:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-ts"><span class="line"><span style="color:#6a737d">/**</span></span>
<span class="line"><span style="color:#6a737d"> * Distribute file, named by natural numbers, in a way that each folder only</span></span>
<span class="line"><span style="color:#6a737d"> * contains `maxEntriesPerFolder` subfolders or files. Returns a list of</span></span>
<span class="line"><span style="color:#6a737d"> * names, where the last one is the file name, all others are folder names.</span></span>
<span class="line"><span style="color:#6a737d"> * </span></span>
<span class="line"><span style="color:#6a737d"> * Example: `distributedFilePath(1003)` results in `[&apos;2&apos;, &apos;1&apos;, &apos;3&apos;]` which </span></span>
<span class="line"><span style="color:#6a737d"> * could be translated to the file path `2/1/3.json`.</span></span>
<span class="line"><span style="color:#6a737d"> */</span></span>
<span class="line"><span style="color:#d73a49">export</span><span style="color:#d73a49"> function</span><span style="color:#6f42c1"> distributedFilePath</span><span style="color:#24292e">(</span><span style="color:#e36209">index</span><span style="color:#d73a49">:</span><span style="color:#005cc5"> number</span><span style="color:#24292e">, </span><span style="color:#e36209">maxEntriesPerFolder</span><span style="color:#d73a49">:</span><span style="color:#005cc5"> number</span><span style="color:#d73a49"> =</span><span style="color:#005cc5"> 1000</span><span style="color:#24292e">)</span><span style="color:#d73a49">:</span><span style="color:#005cc5"> string</span><span style="color:#24292e">[] {</span></span>
<span class="line"><span style="color:#d73a49">  if</span><span style="color:#24292e"> (index </span><span style="color:#d73a49">&lt;</span><span style="color:#005cc5"> 0</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#d73a49">    throw</span><span style="color:#d73a49"> new</span><span style="color:#6f42c1"> Error</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;Only numbers &gt;= 0 supported&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#d73a49">  const</span><span style="color:#005cc5"> names</span><span style="color:#d73a49">:</span><span style="color:#005cc5"> string</span><span style="color:#24292e">[] </span><span style="color:#d73a49">=</span><span style="color:#24292e"> []</span></span>
<span class="line"><span style="color:#d73a49">  do</span><span style="color:#24292e"> {</span></span>
<span class="line"><span style="color:#24292e">    names.</span><span style="color:#6f42c1">unshift</span><span style="color:#24292e">((index </span><span style="color:#d73a49">%</span><span style="color:#24292e"> maxEntriesPerFolder).</span><span style="color:#6f42c1">toString</span><span style="color:#24292e">())</span></span>
<span class="line"><span style="color:#24292e">    index </span><span style="color:#d73a49">=</span><span style="color:#24292e"> Math.</span><span style="color:#6f42c1">floor</span><span style="color:#24292e">(index </span><span style="color:#d73a49">/</span><span style="color:#24292e"> maxEntriesPerFolder)</span></span>
<span class="line"><span style="color:#24292e">  } </span><span style="color:#d73a49">while</span><span style="color:#24292e"> (index </span><span style="color:#d73a49">&gt;</span><span style="color:#005cc5"> 0</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#24292e">  names.</span><span style="color:#6f42c1">unshift</span><span style="color:#24292e">(names.</span><span style="color:#005cc5">length</span><span style="color:#24292e">.</span><span style="color:#6f42c1">toString</span><span style="color:#24292e">())</span></span>
<span class="line"><span style="color:#d73a49">  return</span><span style="color:#24292e"> names</span></span>
<span class="line"><span style="color:#24292e">}</span></span></code></pre><p>Source: <a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/zeed/blob/master/src/common/data/distributed.ts">zeed Framework</a></p><h3 id="online-services-and-peer-to-peer" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#online-services-and-peer-to-peer">Online Services and Peer-To-Peer</a></h3><p>A suitable mix of the database or file system approach can be selected for online services, depending on which is better suited to the selected service. For Dropbox or WebDAV, for example, this would be the file system approach. For Apple CloudKit, the database approach.</p><p>But a simple service of your own is also conceivable, with a REST, WebSocket or other useful interfaces.</p><p>There is also no reason not to synchronize the data via peer-to-peer (P2P) or another local communication channel. After all, the data is identical and can therefore be synchronized with other clients via the fastest route. Redundancy is therefore an advantage and does not make things any more complicated. Theoretically, the sequence and multiple application of changes is also unproblematic with CRDT.</p><h2 id="refinements" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#refinements">Refinements</a></h2><p>There is still potential for improvement in some areas:</p><ul><li>Control of data size per log entry. Collecting several changes for larger packages or splitting a change into several packages if the scope becomes too large.</li><li>Compression or summarization of data.</li><li>Announcement of new clients, e.g. through special log entries. Evaluation using cryptographic methods.</li><li>By adding a logical clock, such as the Lamport clock, entries can be sorted logically, thereby improving the chronology of an entry.</li><li>Writing the data in a single file per client for reasons of resource optimization.</li><li>Through the clever use of cryptographic means, it may even be possible to implement rights management (<a rel="noopener" target="_blank" class="external" href="https://schmiste.github.io/srds06.pdf">Cryptree</a>).</li></ul><h2 id="outlook" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#outlook">Outlook</a></h2><p>I have been using this technique in my apps for several years, for example in the now completed project <a rel="noopener" target="_blank" class="external" href="https://onepile.apperdeck.com/en/help/internal-file-format">Onepile</a>. New projects using this approach will be published soon.</p><p>Simplicity and flexibility seem to me to be the biggest advantages of this approach. As a result, it should also be future-proof and be able to adapt quickly to new technical conditions.</p><p>The following diagram is an example of an ecosystem for a web app:</p><p class="img-wrapper"><img alt width="2976" height="2536" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/ued9nzrc325q2v.png"></p><h2 id="related" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/localfirst-resilient-sync#related">Related</a></h2><ul><li><strong>Discussion of the article on <a rel="noopener" target="_blank" class="external" href="https://news.ycombinator.com/item?id=40772955">HackerNews</a></strong></li><li><a rel="noopener" target="_blank" class="external" href="https://www.youtube.com/watch?v=NMq0vncHJvU&amp;t=2s">Martin Kleppmann’s Talk on the Local-First Conference 2024 in Berlin</a></li><li><a rel="noopener" target="_blank" class="external" href="https://elk.zone/mastodon.social/@martin@nondeterministic.computer/112639441984059657">Mastodon comment of Martin Kleppmann</a></li><li>List of similar approaches:<ul><li><a rel="noopener" target="_blank" class="external" href="https://github.com/MichaelMure/git-bug/blob/master/doc/model.md">git-bug</a></li><li><a rel="noopener" target="_blank" class="external" href="https://remotestorage.io/">remotestorage.io</a></li><li><a rel="noopener" target="_blank" class="external" href="https://tonsky.me/blog/crdt-filesync/">Local, first, forever</a></li><li><a rel="noopener" target="_blank" class="external" href="https://jack-vanlightly.com/analyses/2024/4/29/understanding-delta-lakes-consistency-model">Understanding Delta Lake’s consistency model</a></li><li><a rel="noopener" target="_blank" class="external" href="http://archagon.net/blog/2018/03/24/data-laced-with-history/">Data Laced with History: Causal Trees &amp; Operational CRDTs</a></li></ul></li></ul></div><p><em>Published on June 24, 2024</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2024-06-24T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Preventing fake news]]></title>
        <id>https://holtwick.de/blog/fake-news</id>
        <link href="https://holtwick.de/blog/fake-news"/>
        <updated>2024-02-07T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to prevent fake news by signing messages.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>I recently read about a “massive fake news machine” that the German Foreign Ministry claims to have uncovered (<a href="https://holtwick.de/blog/fake-news#sources">see below</a>). It can hardly be distinguished from legitimate news.</p><p>It occurred to me spontaneously that if you are not able to <em>stop</em> manipulated news, you should at least be able to <em>identify</em> legitimate news.</p><p><strong>The idea is simple: let’s create a digital network of trust using cryptographic means.</strong></p><h3 id="from-here-it-gets-technical%E2%80%A6" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/fake-news#from-here-it-gets-technical%E2%80%A6">From here it gets technical…</a></h3><p>The simplest form of signing is the calculation of a hash values. So let’s assume I want to tweet as <a rel="noopener" target="_blank" class="external" href="https://twitter.com/holtwick">@holtwick</a>: “I don’t like fake news!” and my secret would be “Gurkensalat” (usually called “salt”). In the terminal it would go like this:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-sh"><span class="line"><span style="color:#005cc5">echo</span><span style="color:#005cc5"> -n</span><span style="color:#032f62"> &quot;@holtwick I don&apos;t like fake news! Gurkensalat&quot;</span><span style="color:#d73a49"> |</span><span style="color:#6f42c1"> shasum</span><span style="color:#005cc5"> -a</span><span style="color:#005cc5"> 256</span></span></code></pre><p>Alternatively with OpenSSL:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-sh"><span class="line"><span style="color:#005cc5">echo</span><span style="color:#005cc5"> -n</span><span style="color:#032f62"> &quot;@holtwick I don&apos;t like fake news! Gurkensalat&quot;</span><span style="color:#d73a49"> |</span><span style="color:#6f42c1"> openssl</span><span style="color:#032f62"> dgst</span><span style="color:#005cc5"> -sha256</span></span></code></pre><p>The result is:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-text"><span class="line"><span>55bc8fe5493377787bfc0be29417fd692070dc8446b855d62a9fedca5b536d53</span></span></code></pre><p>This can also be calculated before I tweet it. Then I take part of the result, e.g. the first 8 characters, and send it with the message, perhaps with a distinguishing feature such as a preceding <code>~</code> character:</p><div class="markdown-alert markdown-alert-example"><p class="markdown-alert-title">@holtwick</p><p>I don’t like fake news! ~55bc8fe5</p></div><p>Now it’s time to verify something like this. A small service on your own website would be conceivable. Here is a small example in PHP of what an online check could look like:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-php"><span class="line"><span style="color:#d73a49">&lt;?</span><span style="color:#005cc5">php</span><span style="color:#24292e"> </span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d">// This one is top secret :)</span></span>
<span class="line"><span style="color:#24292e">$salt </span><span style="color:#d73a49">=</span><span style="color:#032f62"> &quot;Gurkensalat&quot;</span><span style="color:#24292e">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d">// Normalize whitespace to avoid copy paste issues</span></span>
<span class="line"><span style="color:#24292e">$sample </span><span style="color:#d73a49">=</span><span style="color:#005cc5"> trim</span><span style="color:#24292e">(</span><span style="color:#005cc5">preg_replace</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;/</span><span style="color:#032f62">[\t\n\r\s]</span><span style="color:#d73a49">+</span><span style="color:#032f62">/&apos;</span><span style="color:#24292e">, </span><span style="color:#032f62">&apos; &apos;</span><span style="color:#24292e">, $_GET[</span><span style="color:#032f62">&quot;text&quot;</span><span style="color:#24292e">])) </span><span style="color:#d73a49">.</span><span style="color:#032f62"> &quot; &quot;</span><span style="color:#d73a49"> .</span><span style="color:#24292e"> $salt;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d">// Calculate SHA256 and only use the first 8 chars</span></span>
<span class="line"><span style="color:#24292e">$hash </span><span style="color:#d73a49">=</span><span style="color:#005cc5"> substr</span><span style="color:#24292e">(</span><span style="color:#005cc5">hash</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;sha256&apos;</span><span style="color:#24292e">,  $sample), </span><span style="color:#005cc5">0</span><span style="color:#24292e">, </span><span style="color:#005cc5">8</span><span style="color:#24292e">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6a737d">// Compare hashes and return result</span></span>
<span class="line"><span style="color:#005cc5">echo</span><span style="color:#005cc5"> strcmp</span><span style="color:#24292e">($_GET[</span><span style="color:#032f62">&quot;hash&quot;</span><span style="color:#24292e">], $hash) </span><span style="color:#d73a49">==</span><span style="color:#005cc5"> 0</span><span style="color:#d73a49"> ?</span><span style="color:#032f62"> &quot;ok&quot;</span><span style="color:#d73a49"> :</span><span style="color:#032f62"> &quot;invalid&quot;</span><span style="color:#24292e">;</span></span>
<span class="line"><span style="color:#d73a49">?&gt;</span></span></code></pre><p>The resulting URL would then be: <code>https://holtwick.de/experiments/id.php?text=@holtwick%20I%20don%27t%20like%20fake%20news!&amp;hash=55bc8fe5</code></p><h3 id="try-it-out%3A" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/fake-news#try-it-out%3A">Try it out:</a></h3><p>Ok, that was a relatively primitive implementation to illustrate the idea. For a serious application, other techniques would certainly be used, such as a public key procedure or blockchains. Of course, there are already services that do something similar, as this <a rel="noopener" target="_blank" class="external" href="https://www.elektronische-vertrauensdienste.de/EVD/DE/Uebersicht_eVD/start.html">overview from the Federal Network Agency</a> shows.</p><div class="markdown-alert markdown-alert-warning"><p class="markdown-alert-title">Warning</p><p>I’m no cryptography expert and I’m sure I’ve overlooked some attack vectors, but I still think that there are technical possibilities that can at least make it more difficult to claim things in a false name.</p></div><h2 id="sources" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/fake-news#sources">Source</a></h2><ul><li><a rel="noopener" target="_blank" class="external" href="https://www.dw.com/en/how-russian-fake-news-paints-the-germans/a-64394917">DW</a> (engl.)</li><li><a rel="noopener" target="_blank" class="external" href="https://www.theguardian.com/world/2024/jan/26/germany-unearths-pro-russia-disinformation-campaign-on-x">The Guardian</a> (engl.)</li><li><a rel="noopener" target="_blank" class="external" href="https://www.spiegel.de/politik/deutschland/desinformation-aus-russland-auswaertiges-amt-deckt-pro-russische-kampagne-auf-a-765bb30e-8f76-4606-b7ab-8fb9287a6948">Spiegel</a> (dt.)</li><li><a rel="noopener" target="_blank" class="external" href="https://www.tagesschau.de/inland/desinformation-kampagne-russland-100.html">Tagesschau</a> (dt.)</li></ul></div><p><em>Published on February 7, 2024</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2024-02-07T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Website SSG]]></title>
        <id>https://holtwick.de/blog/website-ssg</id>
        <link href="https://holtwick.de/blog/website-ssg"/>
        <updated>2023-12-06T07:00:00.000Z</updated>
        <summary type="html"><![CDATA[How this website is built from Vue and Markdown as a static site, with dynamic components inside posts and Obsidian as the editor for all content.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>After a few years, it was time to update the website. For the last version, I had even developed my own static website generator called <a href="https://holtwick.de/blog/birth-of-hostic">Hostic</a> and everything went wonderfully. But even though I love reinventing the wheel, I only did it halfway for this new attempt.</p><p>Basically, this website consists of <a rel="noopener" target="_blank" class="external" href="https://vuejs.org/">Vue</a> and <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/Markdown">Markdown</a>. A static version of this is created so that the corresponding page with content is available for each URL. This allows the website to be easily indexed by search engines and the loading time is very short. The process is called <a rel="noopener" target="_blank" class="external" href="https://vitejs.dev/guide/ssr">SSG</a> (Server-Side Rendering).</p><p>The highlight, however, is that this approach allows dynamic elements to be integrated into the website and even individual posts. A first example is the interactive registration for my e-mail newsletter, because I simply insert <code>&lt;AppNewsletter/&gt;</code> at this point:</p><div><a target="_blank" href="https://newsletter.holtwick.de/subscription/form">https://newsletter.holtwick.de/subscription/form</a></div><p>Another thing that makes things easier is the use of <a rel="noopener" target="_blank" class="external" href="https://obsidian.md/">Obsidian</a> as a Markdown editor for the content. I use this practical tool every day anyway and can thus easily maintain the content.</p><div class="markdown-alert markdown-alert-info"><p class="markdown-alert-title">Obsidian Callouts</p><p>Obsidian’s <a rel="noopener" target="_blank" class="external" href="https://help.obsidian.md/Editing+and+formatting/Callouts">Callouts</a> are also supported. Also known as <a rel="noopener" target="_blank" class="external" href="https://github.com/orgs/community/discussions/16925">Github Alerts</a>. This is made possible by the practical <a rel="noopener" target="_blank" class="external" href="https://github.com/antfu/markdown-it-github-alerts">Markdown Plugin</a>.</p></div><p>I derived my own approach from the great project <a rel="noopener" target="_blank" class="external" href="https://github.com/antfu/vitesse">Vitesse</a>. The technique used is almost identical, but I had to make a few adjustments for my purposes.</p></div><p><em>Published on December 6, 2023</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2023-12-06T07:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Video Chat Briefing - More features]]></title>
        <id>https://holtwick.de/blog/briefing</id>
        <link href="https://holtwick.de/blog/briefing"/>
        <updated>2021-04-16T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[News feature in Briefing video chat app]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>Last year I started video chat projects. First <a rel="noopener" target="_blank" class="external" href="https://peer-school.apperdeck.com/">peer.school</a> with which I wanted to contribute to the education of younger children. Then from that <a rel="noopener" target="_blank" class="external" href="https://brie.fi/ng">Brie.fi/ng</a>, which focuses on secure communication.</p><p>Briefing in particular has found many friends on <a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/briefing/">Github</a> and is now available in several languages, including Chinese and very recently Russian.</p><p>The project has a very simple design and uses the Javascript framework <a rel="noopener" target="_blank" class="external" href="https://vuejs.org/">Vue</a>. All the necessary functions are available, plus even such exciting features as hiding or swapping a person’s background.</p><p>Some native apps are also available, but among them the <a rel="noopener" target="_blank" class="external" href="https://apps.apple.com/app/briefing-video-chat/id1510803601">iOS App</a> with support for AppClips (enter rooms via QR code or NFC transmitter) is especially worth mentioning.</p><h2 id="embedding" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/briefing#embedding">Embedding</a></h2><p>Besides sharing a URL to enter a shared room, there is also the possibility to embed Briefing directly into your own website. A simple <a rel="noopener" target="_blank" class="external" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe">IFrame</a> makes it possible, like this one:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-html"><span class="line"><span style="color:#24292e">&lt;</span><span style="color:#22863a">iframe</span></span>
<span class="line"><span style="color:#6f42c1">  src</span><span style="color:#24292e">=</span><span style="color:#032f62">&quot;https://brie.fi/ng/cool-call-62?audio=1&amp;video=1&amp;fs=0&amp;invite=0&amp;prefs=0&amp;share=0&quot;</span></span>
<span class="line"><span style="color:#6f42c1">  allow</span><span style="color:#24292e">=</span><span style="color:#032f62">&quot;camera; microphone; fullscreen; speaker; display-capture&quot;</span></span>
<span class="line"><span style="color:#24292e">&gt;&lt;/</span><span style="color:#22863a">iframe</span><span style="color:#24292e">&gt;</span></span></code></pre><p>For easy configuration, there is now an <a rel="noopener" target="_blank" class="external" href="https://brie.fi/ng/embed-demo">IFrame Tool</a> where anyone can quickly configure their code themselves with a few clicks:</p><p class="img-wrapper"><a rel="noopener" target="_blank" class="external" href="https://brie.fi/ng/embed-demo"><img alt="Screenshot" width="555" height="728" fetchpriority="high" decoding="async" src="https://holtwick.de/assets/e2h6ou5ggjt1r8.png"></a></p><p>If you want to have full control, you can also install all the components that are necessary for operation yourself. Instructions can be found in the <a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/briefing/#readme">README on Github</a>.</p><h2 id="commercial-license" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/briefing#commercial-license">Commercial license</a></h2><p>Briefing is open source, but under the <a rel="noopener" target="_blank" class="external" href="https://eupl.eu/">EUPL v1.2 license</a>, which is a European answer to the <a rel="noopener" target="_blank" class="external" href="https://www.gnu.org/licenses/gpl-3.0.en.html">GPL</a>. This means that changes have to be published under the same license.</p><p>However, if it is desired not to publish changes, it is certainly fair to acquire a commercial license that offers these freedoms. Everything about this can also be found in the <a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/briefing/#commercial-license">README</a>.</p><h2 id="future" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/briefing#future">Future</a></h2><p>Currently I’m working on a third product that should follow in the footsteps of Briefing. The focus is on better scalability, privacy and encryption, and relatime collaboration. The development is already well advanced. More about that soon here…</p></div><p><em>Published on April 16, 2021</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2021-04-16T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[LanguageTool]]></title>
        <id>https://holtwick.de/blog/languagetool</id>
        <link href="https://holtwick.de/blog/languagetool"/>
        <updated>2021-04-15T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[Install LanguageTool yourself for better privacy protection]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p><a rel="noopener" target="_blank" class="external" href="https://languagetool.org/">LanguageTool</a> is the secret weapon for producing error-free texts in different languages. Personally, I’m at war with many a grammar rule and am glad when the machine warns me before it gets embarrassing 😅</p><p>However, I believe not everything I wrote needs to be sent to a service that is great, but not under my control.</p><p>So here are some tips to set up and use an appropriate server yourself.</p><h2 id="setting-up-your-own-server" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/languagetool#setting-up-your-own-server">Setting up your own server</a></h2><p>The easiest way to set up your own server is via Docker. This package offers itself: <a rel="noopener" target="_blank" class="external" href="https://hub.docker.com/r/erikvl87/languagetool?ref=login">erikvl87/languagetool</a>. And this is how it is loaded and started:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-sh"><span class="line"><span style="color:#6f42c1">docker</span><span style="color:#032f62"> pull</span><span style="color:#032f62"> erikvl87/languagetool</span></span>
<span class="line"><span style="color:#6f42c1">docker</span><span style="color:#032f62"> run</span><span style="color:#005cc5"> -d</span><span style="color:#005cc5"> --restart</span><span style="color:#032f62"> unless-stopped</span><span style="color:#005cc5"> -p</span><span style="color:#032f62"> 8010:8010</span><span style="color:#032f62"> erikvl87/languagetool</span></span></code></pre><p>Great, now the server can be reached at <code>http://example.com:8010</code>. There is more background information at <a rel="noopener" target="_blank" class="external" href="https://dev.languagetool.org/http-server">https://dev.languagetool.org/http-server</a></p><h2 id="use-in-visual-studio-code" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/languagetool#use-in-visual-studio-code">Use in Visual Studio Code</a></h2><p>The extension <a rel="noopener" target="_blank" class="external" href="https://marketplace.visualstudio.com/items?itemName=davidlday.languagetool-linter">LanguageTool Linter</a> provides all necessary functions to work with LanguageTool. Here the server URL can be entered now. The editing of texts and Markdown becomes much more comfortable.</p><p>The following adjustments to the settings have proven useful:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-json"><span class="line"><span style="color:#24292e">{</span></span>
<span class="line"><span style="color:#005cc5">  &quot;languageToolLinter.external.url&quot;</span><span style="color:#24292e">: </span><span style="color:#032f62">&quot;http://example.com:8010&quot;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#005cc5">  &quot;languageToolLinter.hideDiagnosticsOnChange&quot;</span><span style="color:#24292e">: </span><span style="color:#005cc5">true</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#005cc5">  &quot;languageToolLinter.hideRuleIds&quot;</span><span style="color:#24292e">: </span><span style="color:#005cc5">true</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#005cc5">  &quot;languageToolLinter.lintOnChange&quot;</span><span style="color:#24292e">: </span><span style="color:#005cc5">true</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#005cc5">  &quot;languageToolLinter.lintOnOpen&quot;</span><span style="color:#24292e">: </span><span style="color:#005cc5">true</span></span>
<span class="line"><span style="color:#24292e">}</span></span></code></pre><h2 id="use-in-firefox" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/languagetool#use-in-firefox">Use in Firefox</a></h2><p>There are numerous plugins available for browsers and other apps, see <a rel="noopener" target="_blank" class="external" href="https://languagetool.org/#plugins">https://languagetool.org/#plugins</a>. In the Firefox extension, you can enter your own server under “Experimental settings (only for advanced users)”. The URL must be added to the version path, in our example: <code>http://example.com:8010/v2/</code>.</p></div><p><em>Published on April 15, 2021</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2021-04-15T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why I developed a static Website Builder 🤦🏻‍♂️]]></title>
        <id>https://holtwick.de/blog/birth-of-hostic</id>
        <link href="https://holtwick.de/blog/birth-of-hostic"/>
        <updated>2020-09-02T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[I developed the static website generator Hostic. Here I explain why and how I did it.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>I love <a rel="noopener" target="_blank" class="external" href="https://vuejs.org/">Vue.js</a> from <a rel="noopener" target="_blank" class="external" href="https://evanyou.me/">Evan You</a> and I like static websites. Of course there are already solutions to combine these two passions like <a rel="noopener" target="_blank" class="external" href="https://vuepress.vuejs.org/">VuePress</a> or <a rel="noopener" target="_blank" class="external" href="https://nuxtjs.org/">Nuxt</a>. But would I be a programmer if I would choose this simple way?</p><p>Of course I wanted to get to the <em>bleeding edge</em> and was quickly inspired by Evans newest coup: <a rel="noopener" target="_blank" class="external" href="https://github.com/vitejs/vite">vite</a>. It throws the ballast of the <a rel="noopener" target="_blank" class="external" href="https://webpack.js.org/">webpack</a> overboard and does everything right. First I tried my luck with it and <a rel="noopener" target="_blank" class="external" href="https://github.com/vuejs/vitepress">vitepress</a>, but unfortunately that was not quite what I was looking for.</p><p>So I took a step back and looked at the classics of static website generation: <a rel="noopener" target="_blank" class="external" href="https://www.gatsbyjs.com/">Gatsby</a>, <a rel="noopener" target="_blank" class="external" href="https://gohugo.io/">Hugo</a>, <a rel="noopener" target="_blank" class="external" href="https://jekyllrb.com/">Jekyll</a> and <a rel="noopener" target="_blank" class="external" href="https://www.11ty.dev/">11ty</a>. They did everything right too, but everything didn’t come off the shelf as I would like it to. Especially since I had already built my own solution for <a href="https://holtwick.de/blog/static-jquery">SeaSite</a>, with which all my websites were generated.</p><h2 id="what-do-i-want%3F" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/birth-of-hostic#what-do-i-want%3F">What do I want?</a></h2><p>But what was it that I wanted? I have found out the following points for me:</p><ol><li><strong>Speed:</strong> I want to make changes in the code like I did with Vue.js and see the result immediately in the browser.</li><li><strong>Flexibility:</strong> I would like to be able to influence every aspect of the code myself and be able to program. Preferably in Javascript.</li><li><strong>Post processing:</strong> I would like to be able to easily adjust content after it has already been calculated. This was the core principle of SeaSite, which allowed me to optimize images and videos afterwards, but also to run translations of text passages for different language versions.</li></ol><h2 id="how-do-i-do-it%3F" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/birth-of-hostic#how-do-i-do-it%3F">How do I do it?</a></h2><p>Well, at point 1 I had already discovered <a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/esbuild">esbuild</a> in <a rel="noopener" target="_blank" class="external" href="https://github.com/vitejs/vite">vite</a>. It is so incredibly fast that I could not believe it. The result is also reliable and exactly as it should be. <a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/esbuild">esbuild</a> was set as a tool that I wanted to use.</p><p>So I first built a small Node.js script that transpiled a Javascript file. I also built a small library to register routes. The generation of the content should be done on-demand when the website is requested by a simple Express.js webserver. To generate the static pages I would simply generate and save the content for all registered routes. This worked great and took only milliseconds.</p><p>Quickly I wanted to have the comfort of <a rel="noopener" target="_blank" class="external" href="https://github.com/vitejs/vite">vite</a>, i.e. when files change, the browser reloads immediately. With Chokidar I could watch the folder with the JS files and recompile everything via <a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/esbuild">esbuild</a>. With a little trick, the import cache of Node.js could be bypassed and the new JS could be loaded and executed. With socket.io a reload mechanism for the browser was quickly assembled.</p><h2 id="now-everything-should-become-more-beautiful!" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/birth-of-hostic#now-everything-should-become-more-beautiful!">Now everything should become more beautiful!</a></h2><p>I had now finally caught fire and there was no turning back. Then it could also become more beautiful :) Unfortunately I didn’t succeed in integrating Vue.js at the first go, but I also doubted if this would make sense at all. In SeaSite I had already used JSX and JSDOM. For another project I had already written a DOM abstraction, which is very lean. I now extended it in a way that HTML and XML could be generated easily with JSX.</p><p>This made it possible to manipulate the content with simple DOM actions. But how much nicer it would be, if the corresponding nodes could be found by CSS selectors. So I also implemented the css-parse and it worked fine.</p><p>Also a markdown parser was already available from SeaSite and was only extended to provide meta data for the registration of routes while maintaining the pleasant speed.</p><h2 id="open-source!" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/birth-of-hostic#open-source!">Open Source!</a></h2><p>So now everything was on board that was needed and it was time to create a simple unified structure to publish the project. A first goal was to describe the routes with simple data structures to get maximum flexibility. For common formats like HTML, XML, JSON, text and assets convenient methods were created.</p><p>Since everything had the appearance of a web server anyway, which can also spit out static pages, it was obvious to adopt the smart middleware pattern of <a rel="noopener" target="_blank" class="external" href="https://koajs.com/">Koa.js</a>. This way, templates and plugins are easy to realize. A copy of the data structure mentioned serves then as context and the result is expected in the property <code>ctx.body</code>.</p><p>Here it is now, the final project. I would be very happy about help and ideas. Maybe it is not the greatest tool to create static websites, but maybe it is the basis for an even smarter solution that builds on it.</p><p><strong><a rel="noopener" target="_blank" class="external" href="https://github.com/holtwick/hostic">github.com/holtwick/hostic</a></strong></p><p><strong><a rel="noopener" target="_blank" class="external" href="https://www.npmjs.com/package/hostic">https://www.npmjs.com/package/hostic</a></strong></p><p>In the coming posts I will further explore some of the issues that arise when creating a website and how they can be solved with Hostic. The list of current ideas on topics:</p><ul><li>Building a simple static website with Hostic</li><li>Building a blog with Markdown</li><li>Building a multilingual website and localization</li><li>Optimizations for search engines and accessibility</li><li>Hosting: Beaker Browser, see…</li></ul><p>These websites are already driven by Hostic:</p><ul><li><a rel="noopener" target="_blank" class="external" href="https://pdfify.app/">https://pdfify.app</a></li><li><a rel="noopener" target="_blank" class="external" href="https://holtwick.de/">https://holtwick.de</a></li></ul></div><p><em>Published on September 2, 2020</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2020-09-02T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[An IT strategy for Europe 🇪🇺]]></title>
        <id>https://holtwick.de/blog/it-strategy-for-europe</id>
        <link href="https://holtwick.de/blog/it-strategy-for-europe"/>
        <updated>2020-08-26T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[Europe needs a strategy and political will to change this trend. But the potential and the resources are there to make Europe more independent.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><h2 id="starting-point" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#starting-point">Starting point</a></h2><p>The <a rel="noopener" target="_blank" class="external" href="https://moneyinc.com/richest-companies-in-the-world-in-2019/">most valuable</a> and most successful companies are IT companies: Apple, Google, Amazon and Microsoft. All based in the USA. China, another huge market, is the production site of most hardware, but also the seat of companies that successfully take the place of US companies: Alibaba, Tencent and <a rel="noopener" target="_blank" class="external" href="https://en.wikipedia.org/wiki/List_of_largest_Internet_companies">more</a>. Emerging markets also have their own market, where online business takes place. <a rel="noopener" target="_blank" class="external" href="https://www.kaiostech.com/company/our-story/">KaiOS</a> is an example of an innovative model that is hardly known in the first world.</p><p>Where does Europe rank? There are no significant online platforms. Businesses and administrations make themselves dependent on software from the USA and hardware from China. Creative entrepreneurs and talented developers are migrating to the USA. In the long run, Europe will become less relevant as an innovator and remain an importer of technology. This is disastrous.</p><p>Europe needs a strategy and the political will to change this trend. But the potential and the resources are there to make Europe more independent in a changing global world. In the following I would like to present my personal thoughts on such a strategy.</p><h2 id="data" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#data">Data</a></h2><p>Europe should under no circumstances try to copy the USA or China. The existing successful solutions are basically <strong>centralized solutions</strong>. This means that <strong>services</strong> are offered, where the <strong>data</strong> of the customers are in the access of the operating companies. No matter how well the <em>privacy</em> promises on the websites can be read, de facto there is no protection of the data from the companies or the authorities of the country where the company is located - usually the USA or China.</p><p>Data is the commodity of the information age. The first difference that Europe should make in its strategy is the absolute protection of this data. Users should have full access and control over their data so as not to become dependent on individual companies. This can be achieved through <strong>decentralized solutions</strong> and <strong>strong encryption</strong>.</p><h2 id="platform" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#platform">Platform</a></h2><p>It must be possible to display and edit data. Computers, tablets, smartphones and, over time, other devices will be used for this purpose. It is important which operating system is used, because this is the foundation on which all solutions are built. Microsoft, Google and Apple have a [monopoly] (<a rel="noopener" target="_blank" class="external" href="https://de.statista.com/themen/783/betriebssysteme/">https://de.statista.com/themen/783/betriebssysteme/</a>), all of them based in the USA. Europe should focus on its own <strong>free platform</strong>, with <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Linux">Linux</a> and <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Android">Android</a> as a first starting point.</p><p>But besides the operating system, another <em>secret</em> operating system has emerged: the web browser. Originally a European invention, it has revolutionized the access to information on the Internet. In recent years, complexity has increased and most computer services can now be accessed using web technologies. The programming is easy to learn and the knowledge of it is widely spread. Now with <a rel="noopener" target="_blank" class="external" href="https://webassembly.org/">WebAssembly </a>there are no real performance problems anymore.</p><p>Europe should take advantage of the fact that there is the proven web platform. However, after the weakening of Firefox only one browser engine is relevant and this is controlled by <a rel="noopener" target="_blank" class="external" href="https://de.statista.com/statistik/daten/studie/158095/umfrage/meistgenutzte-browser-im-internet-weltweit/">Google and Apple</a>. Europe should develop its own open engine in the short term, for which <a rel="noopener" target="_blank" class="external" href="https://servo.org/">Servo</a> is the obvious choice. In the long term the operating system as a whole could be replaced by a web engine, following the example of <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Google_Chrome_OS">Google Chrome OS</a>.</p><h2 id="hardware" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#hardware">Hardware</a></h2><p>It is also important to be able to trust the devices themselves. In many chips there are <a rel="noopener" target="_blank" class="external" href="https://www.zdnet.com/article/minix-intels-hidden-in-chip-operating-system/">own small operating systems</a>, which take over important functions and normal developers do not have access to the internals. It is important to become more independent in development and production, preferably with an open approach. Why shouldn’t several hardware manufacturers produce the same type of chips? <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/ARM-Architektur">ARM</a> is a successful model in this area. Unfortunately I don’t know enough about this topic to be able to go into more detail. But I think it is important to create a basis that can be trusted and that is not monopolized.</p><h2 id="software" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#software">Software</a></h2><p>The public sector, i.e. administrations and authorities, should be obliged to use and develop open source software. Any solution developed with government funds must be freely and openly accessible and must be able to be operated without limits and free of license costs. The money currently spent on licenses for Windows in Europe alone should be enough to promote software development. Contracts for development and maintenance should in turn be awarded in Europe in order to stimulate and build up expertise and markets here.</p><h2 id="education" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#education">Education</a></h2><p>Both education in dealing with IT and the transfer of knowledge through IT should be strengthened. Especially in times of Corona, the deficits have clearly become apparent. The needs for an educational infrastructure are certainly comparable throughout Europe, but there is no common effort to find a solution as far as I know.</p><p>In the area of learning materials there are now various solutions, for example in Germany <a rel="noopener" target="_blank" class="external" href="https://anton.app/en_us/">Anton App</a>, <a rel="noopener" target="_blank" class="external" href="https://presse.funk.net/format/musstewissen/">Musste Wissen</a> or <a rel="noopener" target="_blank" class="external" href="https://simpleclub.com/">SimpleClub</a>. But also internationally like <a rel="noopener" target="_blank" class="external" href="https://de.khanacademy.org/">Khan Academy</a>. Nevertheless, each teacher prepares his lessons individually and experiences are not effectively shared. A better promotion of such content could improve both traditional and virtual teaching and thus position Europe better, as the level of education increases and becomes more comparable.</p><h2 id="innovation" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/it-strategy-for-europe#innovation">Innovation</a></h2><p>European citizens have repeatedly shown that they are innovative and recognize future issues. <a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Solarindustrie#Krise_seit_2012">Renewable Energy</a> is a good example of this, but at the same time it is also a bad example of the short breath in political support. In the field of mobility, Europe was at the forefront with fossil fuels by optimizing cars, but then missed out on developments, even though many innovations for the engines of the future also [took place] in Europe [<a rel="noopener" target="_blank" class="external" href="https://de.wikipedia.org/wiki/Transrapid">https://de.wikipedia.org/wiki/Transrapid</a>].</p><p>The great challenges of our time are also opportunities that must be taken advantage of. The potential is there, now we need the courage and passion to go our own way and to do so as quickly as possible.</p><hr><p>Links to similar topics:</p><ul><li><a rel="noopener" target="_blank" class="external" href="https://www.heise.de/meinung/Kommentar-Digitale-Souveraenitaet-zum-Schnaeppchenpreis-von-Europa-und-Mozilla-4874038.html?wt_mc=rss.red.ho.ho.atom.beitrag.beitrag">Kommentar: Digitale Souveränität zum Schnäppchenpreis – von Europa und Mozilla</a> by Felix von Leitner, Heise.</li><li><a rel="noopener" target="_blank" class="external" href="https://macwright.com/2020/08/22/clean-starts-for-the-web.html">A clean start for the web</a> by Tom MacWright</li></ul></div><p><em>Published on August 26, 2020</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2020-08-26T06:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[How to transpile at the speed of light 💫]]></title>
        <id>https://holtwick.de/blog/esbuild</id>
        <link href="https://holtwick.de/blog/esbuild"/>
        <updated>2020-08-20T06:00:00.000Z</updated>
        <summary type="html"><![CDATA[For every developer there comes the moment where speed matters. It saves you a relevant amount of time and keeps the flow going.]]></summary>
        <content type="html"><![CDATA[<div class="post-body"><div class="markdown-body"><p>For every developer there comes the moment where speed matters. It saves you a relevant amount of time and keeps the flow going.</p><p><a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/esbuild">esbuild</a> is definitely fast and reduces the built time significantly. And it is nice and simple too, when it comes to set up.</p><h2 id="build" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/esbuild#build">Build</a></h2><p>It can be started from command line or nicely being integrated in a node.js script like this:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-js"><span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> esbuild</span><span style="color:#d73a49"> =</span><span style="color:#6f42c1"> require</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;esbuild&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> options</span><span style="color:#d73a49"> =</span><span style="color:#24292e"> {</span></span>
<span class="line"><span style="color:#24292e">  target: </span><span style="color:#032f62">&apos;node12&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  platform: </span><span style="color:#032f62">&apos;node&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  jsxFactory: </span><span style="color:#032f62">&apos;h&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  jsxFragment: </span><span style="color:#032f62">&apos;hh&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  bundle: </span><span style="color:#005cc5">true</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  outfile: </span><span style="color:#032f62">&apos;out.js&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  sourcemap: </span><span style="color:#032f62">&apos;inline&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  loader: {</span></span>
<span class="line"><span style="color:#032f62">    &apos;.js&apos;</span><span style="color:#24292e">: </span><span style="color:#032f62">&apos;jsx&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#032f62">    &apos;.css&apos;</span><span style="color:#24292e">: </span><span style="color:#032f62">&apos;text&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">  },</span></span>
<span class="line"><span style="color:#24292e">  entryPoints: [</span><span style="color:#032f62">`${</span><span style="color:#24292e">sitePath</span><span style="color:#032f62">}/index.js`</span><span style="color:#24292e">],</span></span>
<span class="line"><span style="color:#24292e">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#d73a49">await</span><span style="color:#24292e"> service.</span><span style="color:#6f42c1">build</span><span style="color:#24292e">(options)</span></span></code></pre><p>This will build a single JS file containing everything that is needed to run. It also translates JSX and uses the function <code>h</code> to create elements. It also loads files ending on <code>.css</code> as plain text. A source map will be written as well. All this is done in a fragment of a second! This is because esbuild is written in Go instead of Javascript, because speed matters sometimes.</p><h2 id="sitemaps" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/esbuild#sitemaps">Sitemaps</a></h2><p>Speaking of source maps the same author of esbuild also wrote a module to support them on node: <a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/node-source-map-support">node-source-map-support</a>.</p><h2 id="testing" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/esbuild#testing">Testing</a></h2><p>Now the setup is almost complete, but how about testing? I usually use <a rel="noopener" target="_blank" class="external" href="https://jestjs.io/">jest</a> for testing and therefore I wanted to get it working here as well. The solutions available did not fit my case, therefore I wrote my own transform:</p><p>First make sure to tell Jest what to do in a <code>package.json</code> section:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-json"><span class="line"><span style="color:#24292e">{</span></span>
<span class="line"><span style="color:#005cc5">  &quot;jest&quot;</span><span style="color:#24292e">: {</span></span>
<span class="line"><span style="color:#005cc5">    &quot;transform&quot;</span><span style="color:#24292e">: {</span></span>
<span class="line"><span style="color:#005cc5">      &quot;^.+\\.jsx?$&quot;</span><span style="color:#24292e">: </span><span style="color:#032f62">&quot;./src/jest-transform.js&quot;</span></span>
<span class="line"><span style="color:#24292e">    },</span></span>
<span class="line"><span style="color:#005cc5">    &quot;testEnvironment&quot;</span><span style="color:#24292e">: </span><span style="color:#032f62">&quot;node&quot;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#005cc5">    &quot;testPathIgnorePatterns&quot;</span><span style="color:#24292e">: [</span></span>
<span class="line"><span style="color:#032f62">      &quot;node_modules/&quot;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#032f62">      &quot;dist/&quot;</span></span>
<span class="line"><span style="color:#24292e">    ]</span></span>
<span class="line"><span style="color:#24292e">  }</span></span>
<span class="line"><span style="color:#24292e">}</span></span></code></pre><p>The transformer looks like this:</p><pre class="shiki shiki-themes github-light github-dark" tabindex="0" style="background-color:#fff;color:#24292e"><code class="language-js"><span class="line"><span style="color:#6a737d">// Inspired by https://github.com/aelbore/esbuild-jest#readme</span></span>
<span class="line"></span>
<span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> fs</span><span style="color:#d73a49"> =</span><span style="color:#6f42c1"> require</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;node:fs&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> esbuild</span><span style="color:#d73a49"> =</span><span style="color:#6f42c1"> require</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;esbuild&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> pkg</span><span style="color:#d73a49"> =</span><span style="color:#6f42c1"> require</span><span style="color:#24292e">(</span><span style="color:#032f62">&apos;../package.json&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#d73a49">const</span><span style="color:#005cc5"> external</span><span style="color:#d73a49"> =</span><span style="color:#24292e"> [</span></span>
<span class="line"><span style="color:#d73a49">  ...</span><span style="color:#24292e">Object.</span><span style="color:#6f42c1">keys</span><span style="color:#24292e">(pkg.dependencies </span><span style="color:#d73a49">??</span><span style="color:#24292e"> {}),</span></span>
<span class="line"><span style="color:#d73a49">  ...</span><span style="color:#24292e">Object.</span><span style="color:#6f42c1">keys</span><span style="color:#24292e">(pkg.devDependencies </span><span style="color:#d73a49">??</span><span style="color:#24292e"> {}),</span></span>
<span class="line"><span style="color:#d73a49">  ...</span><span style="color:#24292e">Object.</span><span style="color:#6f42c1">keys</span><span style="color:#24292e">(pkg.peerDependencies </span><span style="color:#d73a49">??</span><span style="color:#24292e"> {}),</span></span>
<span class="line"><span style="color:#24292e">]</span></span>
<span class="line"></span>
<span class="line"><span style="color:#005cc5">module</span><span style="color:#24292e">.</span><span style="color:#005cc5">exports</span><span style="color:#d73a49"> =</span><span style="color:#24292e"> {</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6f42c1">  getCacheKey</span><span style="color:#24292e">() { </span><span style="color:#6a737d">// Forces to ignore Jest cache</span></span>
<span class="line"><span style="color:#d73a49">    return</span><span style="color:#24292e"> Math.</span><span style="color:#6f42c1">random</span><span style="color:#24292e">().</span><span style="color:#6f42c1">toString</span><span style="color:#24292e">()</span></span>
<span class="line"><span style="color:#24292e">  },</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6f42c1">  process</span><span style="color:#24292e">(</span><span style="color:#e36209">content</span><span style="color:#24292e">, </span><span style="color:#e36209">filename</span><span style="color:#24292e">) {</span></span>
<span class="line"><span style="color:#24292e">    esbuild.</span><span style="color:#6f42c1">buildSync</span><span style="color:#24292e">({</span></span>
<span class="line"><span style="color:#24292e">      target: </span><span style="color:#032f62">&apos;node14&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      platform: </span><span style="color:#032f62">&apos;node&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      jsxFactory: </span><span style="color:#032f62">&apos;h&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      jsxFragment: </span><span style="color:#032f62">&apos;h&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      bundle: </span><span style="color:#005cc5">true</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      outfile: </span><span style="color:#032f62">&apos;out-jest.js&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      sourcemap: </span><span style="color:#032f62">&apos;inline&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      loader: {</span></span>
<span class="line"><span style="color:#032f62">        &apos;.js&apos;</span><span style="color:#24292e">: </span><span style="color:#032f62">&apos;jsx&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#032f62">        &apos;.css&apos;</span><span style="color:#24292e">: </span><span style="color:#032f62">&apos;text&apos;</span><span style="color:#24292e">,</span></span>
<span class="line"><span style="color:#24292e">      },</span></span>
<span class="line"><span style="color:#24292e">      entryPoints: [filename],</span></span>
<span class="line"><span style="color:#24292e">      external,</span></span>
<span class="line"><span style="color:#24292e">    })</span></span>
<span class="line"><span style="color:#d73a49">    const</span><span style="color:#005cc5"> js</span><span style="color:#d73a49"> =</span><span style="color:#24292e"> fs.</span><span style="color:#6f42c1">readFileSync</span><span style="color:#24292e">(file, </span><span style="color:#032f62">&apos;utf-8&apos;</span><span style="color:#24292e">)</span></span>
<span class="line"><span style="color:#24292e">    fs.</span><span style="color:#6f42c1">unlinkSync</span><span style="color:#24292e">(file)</span></span>
<span class="line"><span style="color:#d73a49">    return</span><span style="color:#24292e"> js</span></span>
<span class="line"><span style="color:#24292e">  },</span></span>
<span class="line"><span style="color:#24292e">}</span></span></code></pre><h2 id="competition" tabindex="-1"><a class="header-anchor" href="https://holtwick.de/blog/esbuild#competition">Competition</a></h2><p>Why would you want to you use esbuild and not webpack, babel, rollup, etc.? Well, because it is fast and easy to use. The other solutions are blown up and become pretty complex after a while. They have many 3rd party dependencies, which can cause troubles as well.</p><p>If you want to experience the blatant acceleration, then try <a rel="noopener" target="_blank" class="external" href="https://github.com/evanw/esbuild">esbuild</a>.</p><hr><p><span>Photo by <a target="_blank" rel="noopener noreferrer" class="external" href="https://unsplash.com/@traf?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Traf</a> on <a target="_blank" rel="noopener noreferrer" class="external" href="https://unsplash.com/s/photos/fast?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span></p></div><p><em>Published on August 20, 2020</em></p></div>]]></content>
        <author>
            <name>Dirk Holtwick</name>
            <email>support@holtwick.de</email>
            <uri>https://holtwick.de/</uri>
        </author>
        <published>2020-08-20T06:00:00.000Z</published>
    </entry>
</feed>