<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>kapptec</title><id>https://www.kapp.technology</id><updated>2026-05-14T00:00:00+00:00</updated><author><name>kapptec</name></author><link href="https://www.kapp.technology/atom.xml" rel="self"/><link href="https://www.kapp.technology" rel="alternate"/><entry><title>Wie Bun nach Rust portiert wurde — und was wir daraus über die Zusammenarbeit mit Agenten lernen</title><id>https://www.kapp.technology/blog/how-bun-got-ported-to-rust</id><updated>2026-05-14T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/how-bun-got-ported-to-rust" rel="alternate"/><published>2026-05-14T00:00:00+00:00</published><summary>Der Zig-zu-Rust-Port von Bun wurde in einem einzigen PR mit 2.188 Dateien ausgeliefert. Die dahinter liegenden Claude-Agent-Workflows sind über Git geleakt — zehn Lektionen, die es anzuschauen lohnt.</summary><content type="html">&lt;p&gt;&lt;em&gt;&lt;strong&gt;Nachdem der Agent sich mit &lt;code&gt;background command &quot;Build Bun debug binary&quot; completed (exit code 0)&lt;/code&gt; zurückgemeldet hatte und ich ein aus Rust kompiliertes Bun-Binary in den Händen hielt, gab ich Claude die nächste Aufgabe: &lt;a href=&quot;https://github.com/oven-sh/bun/pull/30412&quot;&gt;diesen PR&lt;/a&gt; zu analysieren und einen Blogbeitrag darüber zu schreiben, wie &lt;a href=&quot;https://x.com/jarredsumner&quot;&gt;Jarred Sumner&lt;/a&gt; den Zig-zu-Rust-Port hingekriegt hat — damit alle anderen lernen können, besser mit Agenten zu arbeiten. Was folgt, ist das, was zurückkam, möglicherweise mit Spuren des Mythos-Modells.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Am 14. Mai 2026 mergte Jarred Sumner &lt;a href=&quot;https://github.com/oven-sh/bun/pull/30412&quot;&gt;PR #30412 — “Rewrite Bun in Rust”&lt;/a&gt; in &lt;code&gt;oven-sh/bun&lt;/code&gt;. Es war einer der grössten einzelnen PRs in der Geschichte des öffentlichen Open Source: &lt;strong&gt;2.188 Dateien geändert, 1.009.257 Zeilen hinzugefügt&lt;/strong&gt;. Eine komplette JavaScript-Runtime — Bundler, Package Manager, HTTP/2/3-Stack, Dev Server, Transpiler, alles — wurde von Zig auf Rust neu implementiert, mit unveränderter Architektur und weiterhin grüner Testsuite.&lt;/p&gt;
&lt;p&gt;Ohne Claude-Agenten wäre das mit hoher Wahrscheinlichkeit nicht möglich gewesen. Der PR sagt es nicht offen — im Beschreibungstext steht nur “compiler-assisted tools for catching &amp;amp; preventing memory bugs” — aber die Commit-Historie verrät die Orchestrierung. Zehn Minuten nach dem Merge pushte Jarred einen weiteren Commit mit dem Titel “Delete stray files”, der 34 Dateien aus &lt;code&gt;.claude/workflows/&lt;/code&gt; entfernte. Diese Dateien, kurzzeitig in der Git-Historie öffentlich sichtbar, erzählen die Geschichte.&lt;/p&gt;
&lt;p&gt;In diesem Beitrag geht es darum, was wir aus dieser Geschichte lernen können — nicht über Bun im Speziellen, sondern darüber, wie man eine Codebase, Instruktionen und Tools so aufsetzt, dass Agenten Arbeit erledigen, die Menschen allein nicht ausliefern könnten.&lt;/p&gt;
&lt;h2&gt;Die geleakten Beweise&lt;/h2&gt;
&lt;p&gt;Das gelöschte Verzeichnis enthielt Workflow-Skripte, deren Namen einer bewussten Phasenreihenfolge folgten:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;phase-a-port.workflow.js
phase-b0-cyclebreak / movein / moveout / verify.workflow.js
phase-b1-tier.workflow.js
phase-b2-cycle / fill / fix-bugs / keystone / ungate-tier / verify.workflow.js
phase-c-panic-swarm.workflow.js
phase-d-build-queue / crate-shard / todo-sweep / unsafe-audit /
       blocked-on-resolve / bundler-perfile / bundler-shard /
       recursive-ungate / subtree-batch.workflow.js
phase-e-body-port / mass-ungate / proper-port / test-bringup /
       scopeguard-sweep.workflow.js
phase-f-probe-swarm / test-swarm.workflow.js
phase-g-mega-swarm / test-swarm.workflow.js
phase-h-ci-tasks / dedup / deep-dive / diff-review / idioms-audit /
       libuv-audit / main-parity / unsafe-wrap.workflow.js
lifetime-classify.workflow.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Und noch eine Datei, &lt;code&gt;.apply-lock-guard.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# Serialize Apply-phase agents on /root/bun-5 working tree.
# Usage: flock /root/bun-5/.apply-lock bash .apply-lock-guard.sh -- &amp;lt;command...&amp;gt;
exec &quot;$@&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ein vierzeiliger &lt;code&gt;flock&lt;/code&gt;-Wrapper, um parallel laufende Agenten zu serialisieren, die auf einem gemeinsamen Working Tree unter &lt;code&gt;/root/bun-5&lt;/code&gt; schreiben. Wer auch immer das ausgeführt hat, liess viele Agenten gleichzeitig am selben Checkout arbeiten und benutzte Dateisystem-Locks, damit sie nicht kollidieren.&lt;/p&gt;
&lt;p&gt;Aufschlussreicher ist der Inhalt von &lt;code&gt;phase-a-port.workflow.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export const meta = {
  name: &quot;phase-a-port&quot;,
  description: &quot;Phase A: draft .rs for a batch of .zig files (implement → verify → fix)&quot;,
  phases: [
    { title: &quot;Implement&quot;, detail: &quot;one agent per .zig writes draft .rs per PORTING.md&quot; },
    { title: &quot;Verify&quot;,    detail: &quot;adversarial check of .rs against .zig + PORTING.md rules&quot; },
    { title: &quot;Fix&quot;,       detail: &quot;apply verifier findings to .rs&quot; },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ein Agent pro Zig-Datei. Ein zweiter, adversarialer Agent verifiziert gegen einen &lt;code&gt;PORTING.md&lt;/code&gt;-Styleguide. Ein dritter wendet die Befunde des Verifiers an. Alle Ausgaben werden gegen ein JSON-Schema validiert (&lt;code&gt;IMPL_SCHEMA&lt;/code&gt;, &lt;code&gt;VERIFY_SCHEMA&lt;/code&gt; werden weiter unten definiert), damit die nächste Phase sie maschinell verarbeiten kann.&lt;/p&gt;
&lt;h2&gt;Was im Git Repo noch sichtbar ist&lt;/h2&gt;
&lt;p&gt;Die Workflows wurden gelöscht, doch ihre Fingerabdrücke sind überall:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.292 &lt;code&gt;.zig&lt;/code&gt;-Dateien bleiben neben 1.432 &lt;code&gt;.rs&lt;/code&gt;-Dateien erhalten.&lt;/strong&gt; Die &lt;code&gt;CLAUDE.md&lt;/code&gt; im Root wird deutlich (Zeile 141): &lt;em&gt;“These are the original Zig implementation, kept only as a porting reference — they are not compiled and not shipped. New code goes in &lt;code&gt;.rs&lt;/code&gt;. When fixing a bug or porting a behavior, the &lt;code&gt;.zig&lt;/code&gt; sibling is the source of truth for intended semantics.”&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;10.841 &lt;code&gt;PORT NOTE:&lt;/code&gt; / &lt;code&gt;PERF(port):&lt;/code&gt; / &lt;code&gt;TODO(port):&lt;/code&gt; Kommentare&lt;/strong&gt; im Rust-Quellcode. Das sind im Schnitt sieben bis acht pro Datei. Sie sind nicht dekorativ. Schau dir &lt;code&gt;src/bundler/defines.rs&lt;/code&gt; an:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// PORT NOTE: Zig pre-counted `key_buf_len`/`e_strings_to_allocate` to size
// two bump allocations, then `iter.reset()` and re-walked. With per-entry
// copies the pre-sizing pass is dead — emit directly.
// PERF(port): was single-buffer key arena; now per-entry Vec reuse.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jede Annotation erklärt, &lt;em&gt;warum&lt;/em&gt; der Rust-Code vom Zig-Code abweicht — für den nächsten Agenten, der die Datei liest.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;108 Cargo-Crates&lt;/strong&gt; in einem einzigen Workspace, mit Präfixen &lt;code&gt;bun_core&lt;/code&gt;, &lt;code&gt;bun_sys&lt;/code&gt;, &lt;code&gt;bun_paths&lt;/code&gt;, &lt;code&gt;bun_jsc&lt;/code&gt;, &lt;code&gt;bun_runtime&lt;/code&gt;, &lt;code&gt;bun_js_parser&lt;/code&gt;, &lt;code&gt;bun_js_printer&lt;/code&gt;, &lt;code&gt;bun_bundler&lt;/code&gt;, &lt;code&gt;bun_install&lt;/code&gt;, … Winzige Crates sind nicht bloss eine architektonische Entscheidung — sie sind eine Parallelisierungsgrenze. Jedes Crate ist eine geshardete Arbeitseinheit, die ein Agent übernehmen, prüfen und unabhängig mit &lt;code&gt;cargo check -p &amp;lt;crate&amp;gt;&lt;/code&gt; fertigstellen kann.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fest verdrahtete Branch-Namen.&lt;/strong&gt; &lt;code&gt;CLAUDE.md&lt;/code&gt; Zeile 266: &lt;em&gt;“Branch names must start with &lt;code&gt;claude/&lt;/code&gt;. This is a requirement for the CI to work.”&lt;/em&gt; Das CI unterscheidet Agenten-Commits von menschlichen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ein vorschreibender Styleguide, getarnt als Entwicklerdokumentation.&lt;/strong&gt; &lt;code&gt;src/CLAUDE.md&lt;/code&gt; (14 KB) enthält eine sechsspaltige Tabelle, die festlegt, welche &lt;code&gt;std::&lt;/code&gt;-APIs verboten sind und was stattdessen zu verwenden ist — &lt;code&gt;bun_sys::File&lt;/code&gt; statt &lt;code&gt;std::fs::File&lt;/code&gt;, &lt;code&gt;bun_paths::dirname&lt;/code&gt; statt &lt;code&gt;Path::parent&lt;/code&gt;, &lt;code&gt;bun_core::env_var::*::get()&lt;/code&gt; statt &lt;code&gt;std::env::var&lt;/code&gt;, und so weiter. Keine Recherche, kein Ermessen. Die Agenten sollen kein “rustiges Äquivalent” suchen. Sie bekommen es vorgeschrieben.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ein “Code-Review-Selbstcheck”-Abschnitt&lt;/strong&gt; in derselben Datei:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Before writing code that makes a non-obvious choice, pre-emptively ask “why this and not the alternative?” If you can’t answer, research until you can — don’t write first and justify later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Und, ganz unverblümt, im Abschnitt Important Notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Be humble &amp;amp; honest&lt;/strong&gt; — NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Den letzten Satz gibt es nur, weil jemand — viele Jemands — übertrieben haben.&lt;/p&gt;
&lt;h2&gt;Die Phasen als Denkmuster&lt;/h2&gt;
&lt;p&gt;Lies die Phasennamen als Satz, nicht als Liste:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt; — einen Erstentwurf implementieren (ein Agent pro Datei)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B0–B2&lt;/strong&gt; — Abhängigkeitszyklen aufbrechen, Typen zwischen Crates verschieben, sie in Schichten sortieren, damit jedes Crate kompiliert&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt; — Panics aus der Codebase fegen (“panic-swarm”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D&lt;/strong&gt; — Arbeit pro Crate, pro Teilbaum, pro Bundler-Datei auffächern; TODOs abarbeiten und &lt;code&gt;unsafe&lt;/code&gt; auditieren&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt; — echte Funktionsrümpfe auf das nun kompilierende Skelett portieren&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;F–G&lt;/strong&gt; — Schwarm-Probes und Tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;H&lt;/strong&gt; — Diff-Review, Deduplizierung, Idiom-Audit, libuv-Parität, &lt;code&gt;unsafe&lt;/code&gt; aufräumen, CI-Cleanup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Das Muster lautet: erst ein Entwurf, dann ein struktureller Durchgang, damit alles kompiliert, dann die Funktionsrümpfe, dann die Tests, dann die Audits. Jede Phase hat andere Invarianten, andere Eingaben, andere Verifier-Prompts. Ein einziger “Portiere diese Codebase”-Mega-Prompt hätte Slop produziert — so heruntergebrochen wird jede Phase zu einem Problem, das klein genug ist, um es einem Agenten mit einem engen Kontrakt zu übergeben.&lt;/p&gt;
&lt;h2&gt;Übertragbare Lektionen&lt;/h2&gt;
&lt;p&gt;Wenn du nur eine Sache mitnimmst, dann diese: Die Workflow-Skripte sind geleakt, weil &lt;strong&gt;die Agenten-Infrastruktur projektspezifisch war, nicht generisch.&lt;/strong&gt; Jarred liess nicht einfach “Claude, portiere mein Repo” laufen. Er baute eine Portierungs-Harness für sein Repo, mit seinen Konventionen, seinem Crate-Layout und seinem Styleguide, und liess die Agenten dann dort hindurchlaufen. Das Modell ist dasselbe, das jeder benutzen kann; die Hebelwirkung kam von der Harness drumherum.&lt;/p&gt;
&lt;p&gt;Konkrete Muster, die es zu klauen lohnt:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Behalte den alten Code als Grundwahrheit im Tree.&lt;/strong&gt; Lösche den Zig-Quellcode nicht an dem Tag, an dem du den Rust-Code eincheckst. Agenten verlieren zwischen Sessions den Faden, was eigentlich beabsichtigt war; das Original ist die einzige stabile Referenz dafür, “was &lt;em&gt;soll&lt;/em&gt; das hier eigentlich tun”. Die 1.292 &lt;code&gt;.zig&lt;/code&gt;-Dateien waren kein totes Gewicht — sie waren die Spezifikation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Annotiere jede Abweichung mit dem WARUM, nicht dem WAS.&lt;/strong&gt; &lt;code&gt;PORT NOTE: reshaped for borrowck — drop deflate borrow before re-borrowing self&lt;/code&gt;. Dem nächsten Agenten muss man nicht sagen, dass Rust einen Borrow-Checker hat. Man muss ihm sagen, dass &lt;em&gt;genau diese Zeile&lt;/em&gt; existiert, weil sich ein vorheriger Agent bereits durch die offensichtliche naive Variante gekämpft hat — und die nicht funktionierte. Ohne das entdeckt jeder Agent dieselben Workarounds neu und verhandelt sie wieder durch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Schreibe einen vorschreibenden Styleguide, keinen aspirativen.&lt;/strong&gt; Wenn du eine Meinung hast, codiere sie als Tabelle mit “verboten/Ersatz”. Agenten sind ausgezeichnet im Regelnbefolgen und miserabel im Geschmackhaben. Lass sie nicht zwischen &lt;code&gt;std::fs::File&lt;/code&gt; und deinem Wrapper wählen — sage &lt;em&gt;du musst den Wrapper benutzen, hier ist der Wrapper, hier ist, wann welche Methode aufgerufen wird&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Trenne Implementieren, Verifizieren und Anwenden.&lt;/strong&gt; Der Phase-A-Workflow hatte drei Rollen pro Datei: Entwerfer, adversarialer Reviewer, Korrektor. Der adversariale Agent sieht nicht das Reasoning des Entwerfers, nur dessen Ausgabe und die Regeln. Das zwingt den Entwerfer zur Explizitheit (weil der Reviewer implizite Annahmen aufdeckt) und stoppt jene Art von selbstbestätigendem Drift, die Single-Agent-Loops produzieren.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Verlange strukturierte Ausgaben zwischen Phasen.&lt;/strong&gt; &lt;code&gt;IMPL_SCHEMA&lt;/code&gt; und &lt;code&gt;VERIFY_SCHEMA&lt;/code&gt; waren nicht optional — der Workflow validiert sie. Wenn Agenten an andere Agenten übergeben, geht in freier Prosa Information verloren. Lass sie ein JSON-Objekt mit &lt;code&gt;rs_path&lt;/code&gt;, &lt;code&gt;confidence&lt;/code&gt;, &lt;code&gt;todos&lt;/code&gt;, &lt;code&gt;rs_loc&lt;/code&gt;, &lt;code&gt;note&lt;/code&gt; ausfüllen. Die nächste Phase parst das, anstatt erneut Aufsätze zu lesen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Sharde die Arbeit für Parallelität, sperre die Schreibzugriffe.&lt;/strong&gt; ~200 kleine Crates machen Parallelität billig, weil die Granularität von &lt;code&gt;cargo check&lt;/code&gt; der Granularität der Agenten entspricht. Ein &lt;code&gt;flock&lt;/code&gt;-Guard um den Working Tree macht die Parallelität &lt;em&gt;sicher&lt;/em&gt;. Beides braucht es — die Architektur und den Mechanismus.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. Kennzeichne Agentenarbeit im CI.&lt;/strong&gt; Branches mit &lt;code&gt;claude/&lt;/code&gt;-Präfix geben dir eine Möglichkeit, von Agenten erzeugte Änderungen getrennt von menschlichen zu gaten, zu zählen und rückgängig zu machen. Das ist die langweilige Art von Infrastruktur, die in dem Moment unverzichtbar wird, in dem dein Agentenvolumen dein Review-Volumen übersteigt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Schreibe explizite Regeln gegen Konfabulation.&lt;/strong&gt; “Übertreibe NIEMALS, was du geschafft hast.” “Frage vorab: warum dies und nicht die Alternative?” Das liest sich wie Führungsratgeber, ist im Kontext aber tragend — die Regeln existieren, weil der Fehlermodus “alle Tests grün” eines Agenten, während sieben Crates gar nicht kompiliert haben, real ist und im System-Prompt adressiert werden muss, nicht erst im Code-Review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9. Mache &lt;code&gt;build&lt;/code&gt; und &lt;code&gt;test&lt;/code&gt; zu einem einzigen Befehl.&lt;/strong&gt; &lt;code&gt;bun bd test &amp;lt;file&amp;gt;&lt;/code&gt; baut und führt aus. Agenten müssen keine Zweischritt-Beschwörungsformel im Kopf behalten, also können sie nicht scheitern, weil sie Schritt zwei vergessen. “Bring deine Tests zum Bestehen. Wenn du die Tests nicht ausgeführt hast, funktioniert dein Code nicht.” — diese Anweisung wirkt nur, wenn Tests ausführen ein einziger Tool-Call ist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10. Behandle die Meta-Infrastruktur als Teil der Arbeit.&lt;/strong&gt; Das geleakte &lt;code&gt;.apply-lock-guard.sh&lt;/code&gt; umfasst vier Zeilen. Die geleakten Workflows umfassen 4.000. Die CLAUDE.md-Dateien im Repo nochmals 30 KB. Nichts davon wird an die User ausgeliefert. Aber alles davon hat Bun ausgeliefert.&lt;/p&gt;
&lt;h2&gt;Die Fussnote, die du eigentlich willst&lt;/h2&gt;
&lt;p&gt;Die Rust-Binary von Bun ist 3–8 MB kleiner, die Tests laufen weiterhin grün, die Benchmarks sind neutral bis schneller, und die Codebase ist jetzt in einer Sprache, in der der Compiler genau jene Bug-Klasse erkennt, die das Team historisch am meisten Zeit gekostet hat. Der Beweis für den Ansatz sind nicht die Workflows — es ist, dass eine der feindseligsten Software-Engineering-Aufgaben überhaupt (eine systemnahe Codebase von einer Million Zeilen in eine Sprache mit komplett anderem Speichermodell umzuschreiben, ohne die Kompatibilität zu brechen) ausgeliefert wurde, mit allen Tests grün, in einem einzigen PR.&lt;/p&gt;
&lt;p&gt;Wenn du dich fragst, ob Agenten echte Arbeit verrichten können: Sie können. Die Frage ist, ob du bereit bist, den Agenten im Zaum zu halten und ihm ein Geschirr wie für Rösser zu bauen.&lt;/p&gt;
</content></entry><entry><title>So läuft Playwright mit Deno</title><id>https://www.kapp.technology/blog/run-playwright-on-deno-javascript-runtime</id><updated>2024-12-23T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/run-playwright-on-deno-javascript-runtime" rel="alternate"/><published>2024-12-23T00:00:00+00:00</published><summary>Neben Node ist nun die alternative JavaScript Laufzeitumgebungen Deno in 2.1 LTS verfügbar. Wir schauen uns an, wie man das UI Testautomationstool Playwright damit zum Laufen bringt.</summary><content type="html">&lt;h2&gt;JavaScript und Typescript Runtimes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Deno 2.1 LTS ist als Alternative zu Node.js erschienen. Wir testen, wie man das UI-Testautomationstool Playwright damit nutzen kann. Dieser Artikel zeigt die Einrichtung und vergleicht die Performance.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Deno wird wie ‘DEE-noh’ ausgesprochen. Seit Version &lt;a href=&quot;https://github.com/denoland/deno&quot; title=&quot;Deno Project&quot;&gt;Deno&lt;/a&gt; 2.1 wird die Laufzeitumgebung für JavaScript, Typescript und WebAssembly (WASM) in einer Long Term Support (LTS) Variante angeboten, wobei für 6 Monate kritische Bugs und  Sicherheitsprobleme behoben werden, jedoch die APIs stabil bleiben. Diese halbjährlichen Releases werden unter der MIT Lizenz veröffentlicht.&lt;/p&gt;
&lt;p&gt;Da Deno als einzelne Binärdatei ausgeliefert wird, ist die Installation sehr einfach, auch auf CI/CD Pipelines.&lt;/p&gt;
&lt;p&gt;Um die Kompatibilität mit Node zu demonstrieren, nehmen wir die UI Tests für diese Website als Beispiel.&lt;/p&gt;
&lt;p&gt;Diese UI Tests haben wir mittels &lt;a href=&quot;https://playwright.dev/&quot; title=&quot;Playwright&quot;&gt;Playwright&lt;/a&gt; realisiert.&lt;/p&gt;
&lt;h3&gt;Node 22&lt;/h3&gt;
&lt;p&gt;Bei Projekten ohne Playwright kann das Tool mit &lt;code&gt;npm add playwright&lt;/code&gt; hinzugefügt werden.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx playwright install
npx playwright test kapp.technology
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Deno 2.1&lt;/h3&gt;
&lt;p&gt;Bei Node Projekten müssen Pakete mit &lt;code&gt;npm:&lt;/code&gt; Präfix versehen werden. Es ist erforderlich, Deno den Zugriff auf Netzwerk, Disks und Pakete explizit zu geben. Pauschal bequem geht das mit &lt;code&gt;--allow-all&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;deno install
deno run --allow-all npm:playwright install
deno run --allow-all npm:playwright test kapp.technology
deno run --allow-all npm:playwright show-report
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mehr dazu findet man im Github Issue &lt;a href=&quot;https://github.com/denoland/deno/issues/16899#issuecomment-2442248268&quot; title=&quot;NPM: Playwright does not work #16899&quot;&gt;NPM: Playwright does not work #16899&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Das Testscript&lt;/h2&gt;
&lt;p&gt;Das Script verifiziert die grundlegende Korrektheit&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;der lokalen Website&lt;/li&gt;
&lt;li&gt;des Pull bzw Merge Requests und&lt;/li&gt;
&lt;li&gt;der veröffentlichten Website.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { test, expect } from &apos;@playwright/test&apos;;

test(&apos;test&apos;, async ({ page }) =&amp;gt; {

  // Go to https://www.kapp.technology/
  await page.goto(&apos;https://www.kapp.technology/&apos;);

  // Click header &amp;gt;&amp;gt; text=Kapp Technology
  await page.locator(&apos;header &amp;gt;&amp;gt; text=Kapp Technology&apos;).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/&apos;);

  // Click nav &amp;gt;&amp;gt; text=Angebot
  await page.getByRole(&apos;navigation&apos;).getByRole(&apos;link&apos;, { name: &apos;Angebot&apos; }).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/angebot-und-services/&apos;);

  // Click text=Angebot Blog Kontakt
  await page.getByRole(&apos;link&apos;, { name: &apos;Blog&apos; }).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/blog/&apos;);

  // Click text=Der Neubau unserer Website
  await page.getByText(&apos;Der Neubau unserer Website&apos;).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/blog/relaunch-unserer-website/&apos;);

  // Click footer &amp;gt;&amp;gt; text=Angebot
  await page.getByRole(&apos;contentinfo&apos;).getByRole(&apos;link&apos;, { name: &apos;Angebot&apos; }).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/angebot-und-services/&apos;);

  // Click text=Jetzt Kontakt aufnehmen
  await page.getByRole(&apos;link&apos;, { name: &apos;Jetzt Kontakt aufnehmen&apos; }).click();
  await expect(page).toHaveURL(&apos;https://www.kapp.technology/contact/&apos;);

  // Click div[role=&quot;main&quot;]:has-text(&quot;Hallo. Say Hello Senden&quot;)
  await page.locator(&apos;div[role=&quot;main&quot;]:has-text(&quot;Hallo. Say Hello Senden&quot;)&apos;).click();
  await page.getByPlaceholder(&apos;Email Address&apos;).click();
  await page.getByPlaceholder(&apos;Email Address&apos;).fill(&apos;hello@example.com&apos;);
  await page.getByPlaceholder(&apos;What would you like to say?&apos;).fill(&apos;Hello from Test Automation.&apos;);
  await page.getByRole(&apos;button&apos;, { name: &apos;Senden&apos; }).click();
  await expect(page.getByRole(&apos;button&apos;, {
    name: &apos;Nachricht abgeschickt!&apos;
  })).toBeVisible();

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ergebnis&lt;/h2&gt;
&lt;p&gt;Diese spontanen Performance Tests mit nur einem Testscript können höchstens einen Eindruck vermitteln: Beide Plattformen sind etwa gleich schnell.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;JavaScript Platform&lt;/th&gt;&lt;th&gt;Ziel&lt;/th&gt;&lt;th&gt;Command&lt;/th&gt;&lt;th&gt;User&lt;/th&gt;&lt;th&gt;System&lt;/th&gt;&lt;th&gt;CPU&lt;/th&gt;&lt;th&gt;Total&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Deno 2.1&lt;/td&gt;&lt;td&gt;local&lt;/td&gt;&lt;td&gt;deno run –allow-all npm:playwright test local&lt;/td&gt;&lt;td&gt;18.60s&lt;/td&gt;&lt;td&gt;3.37s&lt;/td&gt;&lt;td&gt;186%&lt;/td&gt;&lt;td&gt;11.787&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Deno 2.1&lt;/td&gt;&lt;td&gt;public&lt;/td&gt;&lt;td&gt;deno run –allow-all npm:playwright test kapp.technology&lt;/td&gt;&lt;td&gt;33.92s&lt;/td&gt;&lt;td&gt;6.09s&lt;/td&gt;&lt;td&gt;201%&lt;/td&gt;&lt;td&gt;19.816s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Node 22&lt;/td&gt;&lt;td&gt;local&lt;/td&gt;&lt;td&gt;npx playwright test local&lt;/td&gt;&lt;td&gt;18.11s&lt;/td&gt;&lt;td&gt;2.91s&lt;/td&gt;&lt;td&gt;186%&lt;/td&gt;&lt;td&gt;11.269&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Node 22&lt;/td&gt;&lt;td&gt;public&lt;/td&gt;&lt;td&gt;npx playwright test kapp.technology&lt;/td&gt;&lt;td&gt;33.67s&lt;/td&gt;&lt;td&gt;5.21s&lt;/td&gt;&lt;td&gt;205%&lt;/td&gt;&lt;td&gt;18.931s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Nutzen&lt;/h2&gt;
&lt;p&gt;Als Entwickler schreiben wir die UI Tests lokal und lassen sie dann in den Continuous Integration Pipelines laufen oder gegen die veröffentliche Website laufen.&lt;/p&gt;
&lt;p&gt;Wenn jeder Pull Request wie bei Netlify einfach deployed und getestet werden kann, bringt das viel Gewinn an Sicherheit.  Dann lohnt es sich, in Playwright Testscripts zu investieren.&lt;/p&gt;
</content></entry><entry><title>Ressourcen schonend statische Webauftritte generieren</title><id>https://www.kapp.technology/blog/statische-webseiten-generatoren-in-rust</id><updated>2022-08-04T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/statische-webseiten-generatoren-in-rust" rel="alternate"/><published>2022-08-04T00:00:00+00:00</published><summary>Drei neue Generatoren erzeugen dank Rust sehr effizient statische Websites aus Markdown.</summary><content type="html">&lt;p&gt;&lt;strong&gt;Drei neue Generatoren erzeugen dank Rust sehr effizient statische Websites aus Markdown.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Es gibt eine Reihe von älteren Webseiten Generatoren, die in Rust geschrieben sind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.getzola.org/&quot; title=&quot;Zola&quot;&gt;Zola&lt;/a&gt;, 2018&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cobalt-org/cobalt.rs&quot; title=&quot;Cobalt&quot;&gt;Cobalt&lt;/a&gt;, 2014&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-lang/mdBook&quot; title=&quot;mdbook&quot;&gt;mdBook&lt;/a&gt;, 2015&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wer lernen möchte, wie ein einfacher Generator in Rust geschrieben wird, findet hier einen &lt;a href=&quot;https://kerkour.com/rust-static-site-generator&quot;&gt;interessanten Blog Post&lt;/a&gt; dazu.&lt;/p&gt;
&lt;p&gt;Eine Herausforderung für in Rust geschriebene HTML Generatoren ist, dass sie eine Template Engine brauchen, die zur Laufzeit Content mit Layouts zu HTML Seiten übersetzt.&lt;/p&gt;
&lt;h2&gt;Der Nachwuchs&lt;/h2&gt;
&lt;p&gt;Die aktuelle Generation der in Rust implementierter Website Generatoren hat von der Ergonomie und Einfachheit von  &lt;a href=&quot;https://nextjs.org/&quot;&gt;Nextjs&lt;/a&gt;, &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; und &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt; - alle in Javascript geschrieben - gelernt, so scheint es.&lt;/p&gt;
&lt;h3&gt;Pylon, 2022&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jayson-lennon/pylon&quot;&gt;Pylon&lt;/a&gt; ist ein statischer Website Generator, der sich leicht integrieren lässt in bestehende Shell Skripte. Folgende spezielle Features sind implementiert:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Link Checker&lt;/li&gt;
&lt;li&gt;Datenverzeichnisse in die Website kopieren&lt;/li&gt;
&lt;li&gt;Globale Metadaten können allen Seiten zugänglich gemacht werden.&lt;/li&gt;
&lt;li&gt;Asset im gleichen Verzeichnis wie der Inhalt&lt;/li&gt;
&lt;li&gt;frei definierbare Shortcodes zum Gebrauch im Markdown&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pylon verwendet wie Zola mit &lt;a href=&quot;https://github.com/Keats/tera&quot;&gt;Tera&lt;/a&gt; implementierte &lt;a href=&quot;https://github.com/pallets/jinja&quot;&gt;Jinja&lt;/a&gt; Templates.&lt;/p&gt;
&lt;h3&gt;Oinky, 2021&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/askonomm/oinky&quot;&gt;Oinky&lt;/a&gt; hat verwendet als Template &lt;a href=&quot;https://handlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt;
und übersetzt Markdown Dateien in eine statische HTML Website.&lt;/p&gt;
&lt;p&gt;Die Besonderheit von Oinky ist die DSL welche das erstellen von Datenstrukturen erlaubt, die dann mit Handlebars verarbeitet werden können.&lt;/p&gt;
&lt;h3&gt;Blades, 2020&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/grego/blades&quot;&gt;Blades&lt;/a&gt; ist schnell und reduziert auf die wesentlichen Features. Mit &lt;a href=&quot;https://github.com/maciejhirsz/ramhorns&quot;&gt;Ramhorns&lt;/a&gt; verwendet Blades eine schnelle, &lt;a href=&quot;https://mustache.github.io/&quot;&gt;Mustache&lt;/a&gt; basierte Template Engine, ohne &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt; Ausdrücke oder Schleifen.
Blades behauptet, 10 Mal schneller zu sein als das schnelle &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;, liefert aber keine Benchmarks als Beweis.&lt;/p&gt;
&lt;p&gt;Wie bei Zola werden auch Themes unterstützt, doch die Menge der Stilvorlagen hält sich verglichen mit Hugo in Grenzen.&lt;/p&gt;
&lt;h2&gt;Fazit&lt;/h2&gt;
&lt;p&gt;Wer Zehn- bis Hunderttausende von Markdown Dateien verarbeiten möchte, kann mit den hier vorgestellten Generatoren viel Zeit und vielleicht auch Geld sparen bei einer hochfrequenten Aktualisierung von statischen Websites.&lt;/p&gt;
</content></entry><entry><title>Ein neuer Player: Microsoft Playwright</title><id>https://www.kapp.technology/blog/web-application-testing-playwright</id><updated>2022-05-09T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/web-application-testing-playwright" rel="alternate"/><published>2022-05-09T00:00:00+00:00</published><summary>Web Applikationen testen ist unbeliebt weil mühsam. Testautomatisierung ist eine Voraussetzung für eine flexible Entwicklung einer Web Applikation von hoher Qualität.</summary><content type="html">&lt;p&gt;&lt;strong&gt;Web Applikationen testen ist bei vielen Entwicklern unbeliebt weil mühsam. Testautomatisierung ist eine Voraussetzung für eine flexible Entwicklung einer Web Applikation von hoher Qualität. Features kommen so schneller zu den Usern, weil Changes innert Minuten statt Tagen ausgeliefert werden können. Microsoft hat mit Playwright ein neues  End-to-End-Testing Tool aufgelegt, das Vieles erleichtert.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://playwright.dev/&quot; title=&quot;Playwright&quot;&gt;Playwright&lt;/a&gt;, (deutsch “Dramatiker, Drehbuchschreiber”) soll  ‘jeden’ Webbrowser auf ‘jedem’ Betriebssystem unterstützen. In der Tat kann mit modernen Browsern wie Chrome, Firefox, Safari, Edge unter den Betriebssystemen von Apple, Linux und Windows getestet werden. Dabei werden jeweils die als Open Source erhältlichen Rendering Engines Chromium, WebKit und Firefox eingesetzt. Auch Tests mit den mobilen Browser Versionen unterstützt das Tool.&lt;/p&gt;
&lt;h2&gt;Polyglott&lt;/h2&gt;
&lt;p&gt;Tests können in den wichtigsten Programmiersprachen geschrieben werden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Typescript, JavaScript&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;.NET&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Möglich ist diese Breite durch den Ansatz, auf das &lt;a href=&quot;https://chromedevtools.github.io/devtools-protocol/&quot;&gt;Chrome Debugger Protokoll&lt;/a&gt; zu setzen.&lt;/p&gt;
&lt;p&gt;Tests werden parallel abgearbeitet. Sie warten jeweils, bis der Browser die Seite geladen hat. Entwickler haben einen Testinspektor und einen Testgenerator als Unterstützung zur Hand.&lt;/p&gt;
&lt;p&gt;Playwright kann auch sehr einfach auch Bilder der Webseite erzeugen und als Basis für Testfälle verwenden. Auch Videos vom Testablauf können erzeugt werden.&lt;/p&gt;
&lt;p&gt;Ein Beispiel soll illustrieren wie mit &lt;code&gt;async&lt;/code&gt; und &lt;code&gt;await&lt;/code&gt; nichtblockierende Tests geschrieben werden, hier in JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;	const { test, expect } = require(&apos;@playwright/test&apos;);
	
	test(&apos;basic test&apos;, async ({ page }) =&amp;gt; {  
	
	  await page.goto(&apos;https://github.com/login&apos;);
	
	  await page.fill(&apos;input[name=&quot;login&quot;]&apos;, &apos;USER NAME&apos;);
	
	  await page.fill(&apos;input[name=&quot;password&quot;]&apos;, &apos;PASSWORD&apos;);
	
	  await page.click(&apos;text=Sign in&apos;);
	
	});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Entwickler schreiben die Tests lokal und lassen sie dann in den Continuous Integration Pipelines integriert laufen.&lt;/p&gt;
&lt;h2&gt;Fazit&lt;/h2&gt;
&lt;p&gt;Dadurch, das Playwright auf &lt;code&gt;locator&lt;/code&gt; Funktionen setzt, wird die Brüchigkeit der Tests reduziert.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;test(&apos;display image&apos;, async ({ page }) =&amp;gt; {
  const url = &apos;about/&apos;;
  await page.goto(baseURL + url);
  await expect(page).toHaveURL(baseURL + url);
  console.log(page.url)
  expect(await page.locator(&apos;img[alt=&quot;Andreas Kapp&quot;]&apos;).screenshot()).toMatchSnapshot(&apos;about.png&apos;);

});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eines der grossenHindernisse der Testautomatisierung war bisher, dass Kosten und Aufwand für die Wartung der Tests hoch waren.&lt;/p&gt;
&lt;p&gt;Es scheint nun die Zeit zu sein, wo wir Selenium und Cypress im Rückspiegel verschwinden sehen.&lt;/p&gt;
</content></entry><entry><title>Der Neubau unserer Website</title><id>https://www.kapp.technology/blog/relaunch-unserer-website</id><updated>2019-11-24T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/relaunch-unserer-website" rel="alternate"/><published>2019-11-24T00:00:00+00:00</published><summary>Mit Zola und Rust unterwegs im World Wide Web.</summary><content type="html">&lt;p&gt;Nach dem wir mehrere Jahre mit Jekyll unsere Website gebaut haben, sind wir auf Zola umgestiegen.&lt;/p&gt;
&lt;p&gt;Der Grund: Jekyll verwendet Ruby. Bei jedem Update auf unseren Macs brauchen wir viel Zeit wieder alles zum Laufen zu bringen. Zeit also von Ruby wegzugehen.&lt;/p&gt;
&lt;p&gt;Unser neuer JamStack Webauftritt hat &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; als Programmiersprache im Hintergrund. Das führt zu extrem schelleren Builds in unserer CI/CD Pipeline. Alternativ könnte man auch auf den in Go geschriebenen Static Website Generator &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; setzen, der auch gerade sehr angesagt ist.&lt;/p&gt;
&lt;p&gt;Mehr zu Zola und der Jinja2-basierenden Template Engine Tera finden Sie auf dem &lt;a href=&quot;https://www.getzola.org/&quot;&gt;Projekt Webauftritt&lt;/a&gt;. Das Zola ist gut nutzbar, aber die recht gute Dokumentation hinkt etwas hinterher und ist eher an Entwickler gerichtet.&lt;/p&gt;
</content></entry><entry><title>Was wir gerade tun: PraxKit</title><id>https://www.kapp.technology/blog/was-wir-gerade-tun-praxis-administration-praxkit</id><updated>2017-07-14T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/was-wir-gerade-tun-praxis-administration-praxkit" rel="alternate"/><published>2017-07-14T00:00:00+00:00</published><summary>Die genial einfache Praxis App. Auch für unterwegs.</summary><content type="html">&lt;p&gt;Wir sind anfangs Juni mit &lt;a href=&quot;https://www.praxkit.ch/&quot;&gt;PraxKit&lt;/a&gt; live gegangen. Das Produkt entwickeln wir zusammen mit Andrea von &lt;a href=&quot;http://kapp.ch&quot;&gt;Kapp Coaching &amp;amp; Therapie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Die App umfasst alles was es vom Termin abmachen bis zur Abrechnung braucht.&lt;/p&gt;
&lt;p&gt;Eine attraktive Lösung für die Abrechnung ist enthalten. Unsere Ziele: So einfach und portabel wie möglich. Daher läuft die PraxKit App auf dem Smartphone. Aber auch auf dem PC.&lt;/p&gt;
&lt;p&gt;Zurzeit beschäftigen wir uns gerade intensiv mit dem User Interface Design und der Entwicklung eines einfachen Ablaufs bei der Praxis Administration.&lt;/p&gt;
&lt;p&gt;Unsere Kunden und Zielgruppe sind selbstständige Therapeutinnen und Therapeuten.&lt;/p&gt;
&lt;p&gt;Möchtest Du bei der Entwicklung von Praxkit auf dem Laufenden bleiben, dann registriere dich einfach unter &lt;a href=&quot;https://www.praxkit.ch/#news&quot;&gt;News&lt;/a&gt; oder melde dich für Anregungen zur Praxis Administration einfach über die Website &lt;a href=&quot;https://www.praxkit.ch/#kontakt&quot;&gt;www.praxkit.ch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bis bald!&lt;/p&gt;
</content></entry><entry><title>Announcing Kapp Technology</title><id>https://www.kapp.technology/blog/announcing-kapp-technology</id><updated>2017-03-30T00:00:00+00:00</updated><link href="https://www.kapp.technology/blog/announcing-kapp-technology" rel="alternate"/><published>2017-03-30T00:00:00+00:00</published><summary>Aus Artooa wird Kapp Technology. Unser Angebot..</summary><content type="html">&lt;p&gt;Unser Angebot: Research, Beratung und Software Engineering. Mit frischen Lösungen zu nachhaltiger Veränderung.&lt;/p&gt;
</content></entry></feed>