<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.clauneck.workers.dev</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.clauneck.workers.dev/feed.rss"/>
    <language>en</language>
    <item>
      <title>Part 15: Workflow Patterns and Recipes - Data Transformation</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Fri, 26 Jun 2026 08:03:00 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/homolibere/part-15-workflow-patterns-and-recipes-data-transformation-27ml</link>
      <guid>https://dev.clauneck.workers.dev/homolibere/part-15-workflow-patterns-and-recipes-data-transformation-27ml</guid>
      <description>&lt;p&gt;As we conclude this 15-part series on Vyshyvanka, we want to leave you with practical tools. One of the most common challenges in workflow automation is not moving data, but transforming it. Today, we look at the patterns we use to map, clean, and reshape data as it flows through your pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Transformation Challenge
&lt;/h3&gt;

&lt;p&gt;In a real-world workflow, you rarely get the data exactly in the format you need. Service A might return a deeply nested JSON object, while Service B expects a flat structure with different field names. Vyshyvanka handles this through its expression engine and Code Node — giving you both declarative and programmatic transformation options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Path Projection with Expressions
&lt;/h3&gt;

&lt;p&gt;When you have a large JSON response and only need specific values, use expressions in your node configuration to extract exactly what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.api.data.items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;user.id&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plucks a deeply nested ID from an API response and passes it as a clean input to your next node. You can use this directly in any node's configuration field.&lt;/p&gt;

&lt;p&gt;For more complex extraction, combine with built-in functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;toUpper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nodes.api.data.items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;user.name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nodes.user.data.firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nodes.user.data.lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;nodes.api.data.nickname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nodes.api.data.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Anonymous"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Structural Remapping with Code Nodes
&lt;/h3&gt;

&lt;p&gt;When you need to transform the entire shape of the data, the Code Node is your best tool. It supports two runtimes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript (via Jint engine):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;General-purpose JavaScript for complex transformations. You have access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt; — the full input data from upstream nodes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;executionId&lt;/code&gt; — current execution ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;workflowId&lt;/code&gt; — current workflow ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;log(message)&lt;/code&gt; — logging that appears in execution output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getItems()&lt;/code&gt; — returns input as an array (wraps single values)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toJson(value)&lt;/code&gt; — serializes a value to a JSON string
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Remap an API response to a different structure&lt;/span&gt;
&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Transforming user data: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;createdYear&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JSONata:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A declarative query and transformation language, ideal for complex path selection and restructuring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;items.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$count(items)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSONata shines when you need to reshape deeply nested structures without imperative loop code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Batch Processing with runForEachItem
&lt;/h3&gt;

&lt;p&gt;Both JavaScript and JSONata support two execution modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;runOnce&lt;/code&gt;:&lt;/strong&gt; Executes your code against the full input object. Use this for single-item transformations or when you need access to the entire dataset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;runForEachItem&lt;/code&gt;:&lt;/strong&gt; If your input is an array, the engine executes your logic for every item, collecting the results into a new array.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Mode: runForEachItem&lt;/span&gt;
&lt;span class="c1"&gt;// 'input' here is a single item from the array&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the equivalent of a &lt;code&gt;.map()&lt;/code&gt; operation, but managed by the engine with proper error handling and logging for each item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Conditional Routing with Logic Nodes
&lt;/h3&gt;

&lt;p&gt;Not all transformation is about reshaping data — sometimes you need to route data differently based on its content. The &lt;strong&gt;Switch&lt;/strong&gt; node evaluates a value and routes to different output ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure the switch value: &lt;code&gt;{{ nodes.api.data.status }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Define cases: &lt;code&gt;"active"&lt;/code&gt; → port A, &lt;code&gt;"suspended"&lt;/code&gt; → port B, default → port C&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each downstream branch can then apply its own transformation logic appropriate to that case.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;If&lt;/strong&gt; node handles binary decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Condition: &lt;code&gt;{{ nodes.check.data.amount }}&lt;/code&gt; &amp;gt; threshold&lt;/li&gt;
&lt;li&gt;True branch: process normally&lt;/li&gt;
&lt;li&gt;False branch: send alert&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pattern 5: Aggregation with Loop + Merge
&lt;/h3&gt;

&lt;p&gt;For workflows that need to process a collection and then aggregate the results:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loop Node:&lt;/strong&gt; Iterates over an array, executing a subgraph for each item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subgraph nodes:&lt;/strong&gt; Transform/enrich each item (API calls, lookups, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop completion:&lt;/strong&gt; The loop node collects all outputs from its "done" port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge Node:&lt;/strong&gt; Combines data from multiple branches into a single object.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The loop node exposes per-iteration context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.loop.data.index&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;        // Current iteration index
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.loop.data.item&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;         // Current item
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.loop.data.isFirst&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;      // Boolean
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.loop.data.isLast&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;       // Boolean
&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;nodes.loop.data.totalCount&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;   // Total items
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 6: Data Enrichment Pipeline
&lt;/h3&gt;

&lt;p&gt;A common pattern is fetching additional data for each item in a collection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HttpRequest&lt;/strong&gt; → Get list of orders from API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop&lt;/strong&gt; → For each order:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HttpRequest&lt;/strong&gt; → Fetch customer details by &lt;code&gt;{{ nodes.loop.data.item.customerId }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Node&lt;/strong&gt; → Merge order + customer into enriched object&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DatabaseQuery&lt;/strong&gt; → Store enriched results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step references the previous step's output through expressions, creating a data pipeline that transforms raw API responses into enriched, business-ready objects.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep transformations close to their consumer:&lt;/strong&gt; Transform data in the node configuration or a Code Node immediately before the node that needs it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use expressions for simple extractions:&lt;/strong&gt; &lt;code&gt;{{ nodes.api.data.user.name }}&lt;/code&gt; is clearer than a Code Node for simple path access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Code Nodes for complex logic:&lt;/strong&gt; Multi-field remapping, conditional logic, and calculations belong in Code Nodes where they are testable and readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer JSONata for pure data reshaping:&lt;/strong&gt; When you are only restructuring JSON without side effects, JSONata expressions are more concise than JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use runForEachItem for array transformations:&lt;/strong&gt; It handles iteration, error collection, and result aggregation automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wrapping Up the Series
&lt;/h3&gt;

&lt;p&gt;We hope this series has shown you that Vyshyvanka is built for the real world — secure, extensible, and performant. We started with basic triggers and ended with complex data transformation patterns. The platform gives you the building blocks; how you combine them is up to your creativity.&lt;/p&gt;

&lt;p&gt;The community is the final piece of the puzzle. Now it is your turn. Go build something, share your custom nodes, and let us know what you think.&lt;/p&gt;




&lt;p&gt;Check out the project source code here: &lt;a href="https://github.com/homolibere/Vyshyvanka" rel="noopener noreferrer"&gt;https://github.com/homolibere/Vyshyvanka&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>patterns</category>
      <category>data</category>
      <category>architecture</category>
    </item>
    <item>
      <title>كيفية تقييم أدوات الذكاء الاصطناعي لتحقيق إنتاجية حقيقية — shipping</title>
      <dc:creator>Med Stream</dc:creator>
      <pubDate>Fri, 26 Jun 2026 08:00:15 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/med_stream_93ad6cad6d2bee/kyfy-tqyym-dwt-ldhk-lstny-lthqyq-ntjy-hqyqy-shipping-23n0</link>
      <guid>https://dev.clauneck.workers.dev/med_stream_93ad6cad6d2bee/kyfy-tqyym-dwt-ldhk-lstny-lthqyq-ntjy-hqyqy-shipping-23n0</guid>
      <description>&lt;h1&gt;
  
  
  كيفية تقييم أدوات الذكاء الاصطناعي لتحقيق إنتاجية حقيقية — shipping
&lt;/h1&gt;

&lt;p&gt;لم يعد تطوير البرمجيات المفتوحة المصدر مجرد كتابة أكواد ونشرها على منصات المشاركة، بل أصبح سير عمل متكاملاً يعتمد على التكرار السريع، والأتمتة الذكية، والمراجعة الدقيقة. وفي قلب هذه التحولات تقف مكتبات مثل &lt;strong&gt;huggingface_hub&lt;/strong&gt;، التي أصبحت شرياناً حيوياً يربط بين باحثي الذكاء الاصطناعي ومطوري التطبيقات حول العالم. لكن السؤال الذي يطرح نفسه: كيف يمكن إصدار نسخ جديدة من مثل هذه المكتبات بوتيرة أسبوعية دون المساس باستقرارها أو أمانها؟ الإجابة تكمن في مزيج متوازن من ثلاثة عناصر: &lt;strong&gt;الذكاء الاصطناعي&lt;/strong&gt; الذي يُسرّع الإنتاجية، &lt;strong&gt;الأدوات المفتوحة&lt;/strong&gt; التي تبني البنية التحتية، و&lt;strong&gt;الإنسان في الحلقة&lt;/strong&gt; الذي يضمن أن تبقى القرارات الفنية والأخلاقية في أيدٍ أمينة.&lt;/p&gt;

&lt;h2&gt;
  
  
  الإصدار الأسبوعي: فلسفة التطوير المستمر وقليل التكلفة
&lt;/h2&gt;

&lt;p&gt;عندما يُفكر الفرق التقنية في إطلاق إصدارات جديدة، غالباً ما تتبادر إلى الذهن صورة إصدارات ضخمة تأتي كل بضعة أشهر محملة بمئات التغييرات. لكن هذا النموذج أثبت عبر السنوات أنه يحمل في طياته مخاطر تراكمية؛ فكلما طال الفاصل بين إصدارين، زادت كمية الأكواد التي يجب دمجها واختبارها، وتعقدت عملية تتبع الأخطاء. هنا تبرز فلسفة &lt;strong&gt;الشحن الأسبوعي&lt;/strong&gt; كبديل عملي ومستدام.&lt;/p&gt;

&lt;p&gt;التكرار الأسبوعي لا يعني بالضرورة إضافة ميزات ثورية في كل مرة، بل يعني تقليص حجم التغييرات إلى وحدات صغيرة يمكن فحصها واختبارها بسهولة. فبدلاً من دمج عشرات الطلبات البرمجية دفعة واحدة، يتم دمج كل طلب على حدة بعد اجتيازه لجولات من الفحص والتقييم. هذه الطريقة تمنح الفريق القدرة على اكتشاف الانحدارات في الأداء بشكل مبكر، وتقلل من ضغط العمل المتراكم على المشرفين، وتمنح المستخدمين تجربة أكثر سلاسة في التحديث.&lt;/p&gt;

&lt;p&gt;إن سرعة التكرار هذه تتطلب بنية تحتية مرنة. فالنظام الذي يعتمد على عمليات يدوية في بناء الحزم واختبارها ونشرها لا يمكنه الصمود أمام إيقاع أسبوعي. ومن هنا يأتي دور الأتمتة كشرط أساسي لنجاح هذه الفلسفة، حيث يجب أن تكون عمليات الاختبار والبناء والنشر مؤتمتة بالكامل تقريباً، مع ترك مساحة للتدخل البشري في النقاط الحرجة فقط.&lt;/p&gt;

&lt;h2&gt;
  
  
  الذكاء الاصطناعي: مسرّع الإنتاجية وليس بديلاً عن المبرمج
&lt;/h2&gt;

&lt;p&gt;لطالما كان الحلم هو وجود آلة تكتب البرمجيات بديلة عن البشر، لكن الواقع العملي أثبت أن الذكاء الاصطناعي يتألق أكثر عندما يكون &lt;strong&gt;مساعداً&lt;/strong&gt; وليس &lt;strong&gt;محلّاً&lt;/strong&gt;. في سير عمل مثل سير عمل huggingface_hub، تُستخدم نماذج اللغة الكبيرة وأدوات الذكاء الاصطناعي لتحمل المهام المتكررة والاستهلاكية للوقت، مما يحرر المطورين للتركيز على التصميم المعماري وحل المشكلات المعقدة.&lt;/p&gt;

&lt;p&gt;من أبرز الاستخدامات العملية للذكاء الاصطناعي في هذا السياق:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;صياغة الوثائق والشروحات البرمجية:&lt;/strong&gt; يمكن للنماذج اللغوية أن تقترح مسوّدات أولية للتوثيقات التقنية، أو أن تولد تعليقات توضيحية للدوال البرمجية بناءً على سياقها، مما يوفر ساعات من العمل اليدوي.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;توليد حالات الاختبار:&lt;/strong&gt; عند إضافة ميزة جديدة، يستطيع الذكاء الاصطناعي اقتراح حالات اختبار تغطي المسارات الرئيسية والحالات الحدّية، بناءً على تحليل الكود المُقدّم.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;المراجعة الأولية للأكواد:&lt;/strong&gt; أدوات الذكاء الاصطناعي يمكنها أن تفحص الطلبات البرمجية بحثاً عن أنماط شائعة من الأخطاء، أو مشكلات الأمان البسيطة، أو مخالفات دليل الأسلوب البرمجي، قبل أن تصل إلى عين المراجع البشري.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;لكن رغم هذه القدرات، يبقى الذكاء الاصطناعي محدوداً في فهم السياق الاستراتيجي للمشروع. فهو لا يدرك دائماً تأثير تغيير ما على نظام بيئي واسع من المستخدمين، ولا يمكنه اتخاذ قرارات تتعلق بأولويات الميزات أو التوافقية مع الإصدارات السابقة. لذلك، تظل مرحلة المراجعة البشرية حاجزاً لا يمكن تجاوزه.&lt;/p&gt;

&lt;h2&gt;
  
  
  الأدوات المفتوحة: العمود الفقري لسير العمل الحديث
&lt;/h2&gt;

&lt;p&gt;لا يمكن فصل النجاح في الإصدار المتكرر عن البنية التحتية المفتوحة التي تدعمه. فالاعتماد على أدوات احتكارية مغلقة المصدر يخلق تبعيات قد تعرقل سرعة التطوير أو تُضعف من شفافية العملية أمام المجتمع المطوّر. لهذا السبب، تُبنى سير العمل الحديثة على مجموعة من الأدوات المفتوحة التي يمكن لأي مطور أن يدرسها، ويُحسّنها، ويُكيّفها حسب حاجته.&lt;/p&gt;

&lt;p&gt;في بيئة تطوير مثل بيئة huggingface_hub، يتكامل عدة أدوات لتشكيل سلسة إنتاج متكاملة:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;أنظمة التكامل والنشر المستمر (CI/CD):&lt;/strong&gt; تُستخدم منصات مثل GitHub Actions لبناء خطوط أنابيب برمجية تُفحص كل تغيير يُدفع إلى المستودع. فعندما يفتح مطور طلباً برمجياً، تُشغّل هذه الأنظمة تلقائياً مجموعة من الاختبارات على بيئات متعددة، تغطي إصدارات مختلفة من لغة البرمجة ونظام التشغيل، مما يضمن أن التغيير لا يكسر التوافقية على منصات متباينة.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;أدوات تنسيق الكود والفحص الساكن:&lt;/strong&gt; أدوات مثل Ruff وBlack وMyPy تلعب دوراً حيوياً في الحفاظ على جودة الكود ووحدته الشكلية. فقبل أن يُدمج أي طلب برمجي، يجب أن يجتاز فحوصات تنسيق الكود للتأكد من اتباعه لمعايير المشروع، وفحوصات الكتابة للتأكد من مطابقة الأنواع البرمجية المتوقعة. هذه الفحوصات لا تضمن الأمان المنطقي فحسب، بل تُسهّل على أي مساهم جديد فهم الكود والانضمام إلى المشروع.&lt;/p&gt;

&lt;p&gt;**أدوات إد&lt;/p&gt;

&lt;h2&gt;
  
  
  طريقة تطبيق إضافية
&lt;/h2&gt;

&lt;p&gt;لتحويل الفكرة إلى ممارسة يومية، يمكن البدء بتجربة محدودة لمدة أسبوع. اختر مهمة واحدة فقط، مثل تلخيص بحث، إعداد مسودة، أو مقارنة أدوات. سجّل الوقت المستغرق، الأخطاء التي ظهرت، وما إذا كانت النتيجة أسهل في المراجعة من العمل اليدوي الكامل. بعد ذلك يمكن توسيع الاستخدام تدريجياً إذا كانت الفائدة واضحة.&lt;/p&gt;

&lt;p&gt;من المفيد أيضاً إنشاء قائمة تحقق قصيرة قبل الاعتماد على أي نتيجة: هل المصدر معروف؟ هل توجد أرقام تحتاج إلى تحقق؟ هل توجد معلومات حساسة؟ وهل يمكن شرح النتيجة لشخص آخر بثقة؟ هذه الأسئلة البسيطة تقلل مخاطر الاعتماد الزائد على الأتمتة.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://nexus-ai-blog.com/article/shipping-mqqu2l56" rel="noopener noreferrer"&gt;https://nexus-ai-blog.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ذكاءاصطناعي</category>
      <category>برمجة</category>
      <category>تقنية</category>
    </item>
    <item>
      <title>Building a Solana Trading Bot: Architecture, Sniping Logic, and Low-Latency Execution</title>
      <dc:creator>Anttoni Viitala</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:52:48 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/legendaryangelist/building-a-solana-trading-bot-architecture-sniping-logic-and-low-latency-execution-1nl5</link>
      <guid>https://dev.clauneck.workers.dev/legendaryangelist/building-a-solana-trading-bot-architecture-sniping-logic-and-low-latency-execution-1nl5</guid>
      <description>&lt;p&gt;The Solana ecosystem moves faster than almost any other blockchain network.&lt;/p&gt;

&lt;p&gt;New liquidity pools appear every minute. Meme coins launch, pump, and collapse within hours. Arbitrage windows close in seconds. And for anyone trading manually, the harsh reality is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are too slow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s exactly why I built my own &lt;strong&gt;Solana Trading Bot&lt;/strong&gt; — a production-ready, open-source sniper bot designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated token sniping&lt;/li&gt;
&lt;li&gt;Liquidity-based filtering&lt;/li&gt;
&lt;li&gt;Mint authority verification&lt;/li&gt;
&lt;li&gt;Take-profit / stop-loss execution&lt;/li&gt;
&lt;li&gt;Low-latency transaction propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub Repository:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/legendaryangelist/solana-trading-bot-v3" rel="noopener noreferrer"&gt;https://github.com/legendaryangelist/solana-trading-bot-v3&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, I’ll break down the architecture, the technical decisions behind it, and what I learned building automated trading infrastructure for Solana.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build a Solana Trading Bot?
&lt;/h2&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;p&gt;Search “Solana trading bot” and you’ll find dozens of closed-source bots promising profitability.&lt;/p&gt;

&lt;p&gt;The problem?&lt;/p&gt;

&lt;p&gt;Most of them hide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execution logic&lt;/li&gt;
&lt;li&gt;Risk management systems&lt;/li&gt;
&lt;li&gt;Transaction routing methods&lt;/li&gt;
&lt;li&gt;Token filtering mechanisms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developers, that’s a black box.&lt;/p&gt;

&lt;p&gt;I wanted something transparent.&lt;/p&gt;

&lt;p&gt;Something customizable.&lt;/p&gt;

&lt;p&gt;Something optimized specifically for Solana’s performance model.&lt;/p&gt;

&lt;p&gt;Unlike EVM chains, Solana introduces:&lt;/p&gt;

&lt;p&gt;Parallel transaction execution&lt;br&gt;
Priority fee markets&lt;br&gt;
Account-based locking&lt;br&gt;
WebSocket-heavy event systems&lt;/p&gt;

&lt;p&gt;This makes bot development fundamentally different.&lt;/p&gt;

&lt;p&gt;Building on Solana is less about writing simple buy/sell logic and more about &lt;strong&gt;building infrastructure for speed&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Architecture Overview
&lt;/h2&gt;

&lt;p&gt;This bot follows an event-driven architecture.&lt;/p&gt;

&lt;p&gt;Instead of polling for opportunities, it listens to market creation events in real time.&lt;/p&gt;

&lt;p&gt;The system can be broken into five main layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Market Discovery Layer
&lt;/h3&gt;

&lt;p&gt;The bot monitors newly created liquidity pools as they go live.&lt;/p&gt;

&lt;p&gt;Key responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect new pools instantly&lt;/li&gt;
&lt;li&gt;Cache market states&lt;/li&gt;
&lt;li&gt;Avoid duplicate processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is critical because most profitable entries happen within the first few blocks.&lt;/p&gt;

&lt;p&gt;To optimize this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing markets can preload into memory&lt;/li&gt;
&lt;li&gt;New markets are cached dynamically&lt;/li&gt;
&lt;li&gt;WebSocket subscriptions reduce latency significantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This removes expensive repeated RPC queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. RPC Infrastructure Layer
&lt;/h3&gt;

&lt;p&gt;A Solana bot lives or dies by its RPC provider.&lt;/p&gt;

&lt;p&gt;I built support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS RPC endpoints&lt;/li&gt;
&lt;li&gt;WebSocket RPC endpoints&lt;/li&gt;
&lt;li&gt;Commitment-level tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recommended providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helius&lt;/li&gt;
&lt;li&gt;QuickNode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because public RPC nodes fail under high load.&lt;/p&gt;

&lt;p&gt;Private RPC infrastructure improves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster account fetches&lt;/li&gt;
&lt;li&gt;Better slot synchronization&lt;/li&gt;
&lt;li&gt;Reduced dropped transactions&lt;/li&gt;
&lt;li&gt;Lower latency under congestion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This directly affects profitability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution Pipeline
&lt;/h2&gt;

&lt;p&gt;Execution speed is everything.&lt;/p&gt;

&lt;p&gt;Here’s the internal buy pipeline:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;New Pool Detected&lt;br&gt;
        ↓&lt;br&gt;
Filter Validation&lt;br&gt;
        ↓&lt;br&gt;
Liquidity Check&lt;br&gt;
        ↓&lt;br&gt;
Mint Authority Check&lt;br&gt;
        ↓&lt;br&gt;
Social Verification&lt;br&gt;
        ↓&lt;br&gt;
Buy Trigger&lt;br&gt;
        ↓&lt;br&gt;
Transaction Build&lt;br&gt;
        ↓&lt;br&gt;
Priority Fee Injection&lt;br&gt;
        ↓&lt;br&gt;
Submission&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Every stage is optimized to reduce unnecessary delay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Token Filtering System
&lt;/h2&gt;

&lt;p&gt;This is where most bots fail.&lt;/p&gt;

&lt;p&gt;Speed without filtering is just fast gambling.&lt;/p&gt;

&lt;p&gt;I designed multiple defensive filters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mint Renounced Check
&lt;/h3&gt;

&lt;p&gt;One of the most important anti-rug checks.&lt;/p&gt;

&lt;p&gt;If mint authority exists:&lt;/p&gt;

&lt;p&gt;Supply can be increased&lt;br&gt;
Tokenomics can be destroyed instantly&lt;/p&gt;

&lt;p&gt;The bot verifies whether mint authority is revoked before buying.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freeze Authority Detection
&lt;/h3&gt;

&lt;p&gt;A token can freeze holders.&lt;/p&gt;

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

&lt;p&gt;You buy.&lt;/p&gt;

&lt;p&gt;You can’t sell.&lt;/p&gt;

&lt;p&gt;The bot detects freeze authority and rejects those tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metadata Mutability Check
&lt;/h3&gt;

&lt;p&gt;If metadata is mutable:&lt;/p&gt;

&lt;p&gt;Token name can change&lt;br&gt;
Symbol can change&lt;br&gt;
Metadata can be manipulated&lt;/p&gt;

&lt;p&gt;Immutable metadata increases trust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Burned Liquidity Validation
&lt;/h3&gt;

&lt;p&gt;Liquidity burn verification helps detect rug-pull vectors.&lt;/p&gt;

&lt;p&gt;If LP tokens remain under owner control:&lt;/p&gt;

&lt;p&gt;Exit risk is high.&lt;/p&gt;

&lt;p&gt;This bot checks for burned pools before entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Social Presence Validation
&lt;/h3&gt;

&lt;p&gt;Not a security check — but a strong signal.&lt;/p&gt;

&lt;p&gt;The bot can validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twitter/X&lt;/li&gt;
&lt;li&gt;Telegram&lt;/li&gt;
&lt;li&gt;Website links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In memecoin markets, social presence often correlates with initial community strength.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration System
&lt;/h2&gt;

&lt;p&gt;One of my main goals was flexibility.&lt;/p&gt;

&lt;p&gt;Everything is configurable via environment variables.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;QUOTE_AMOUNT=0.5&lt;br&gt;
BUY_SLIPPAGE=5&lt;br&gt;
AUTO_SELL=true&lt;br&gt;
TAKE_PROFIT=30&lt;br&gt;
STOP_LOSS=15&lt;br&gt;
CHECK_IF_MINT_IS_RENOUNCED=true&lt;br&gt;
CHECK_IF_BURNED=true&lt;br&gt;
MIN_POOL_SIZE=10&lt;br&gt;
MAX_POOL_SIZE=200&lt;/p&gt;

&lt;p&gt;This makes strategy iteration extremely fast.&lt;/p&gt;

&lt;p&gt;No code changes required.&lt;/p&gt;

&lt;p&gt;Just configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction Optimization: Warp and Jito
&lt;/h2&gt;

&lt;p&gt;Solana’s competitive trading environment often makes normal RPC submission too slow.&lt;/p&gt;

&lt;p&gt;That’s why I added support for:&lt;/p&gt;

&lt;h3&gt;
  
  
  Warp Execution
&lt;/h3&gt;

&lt;p&gt;Warp routes transactions through hosted infrastructure optimized for faster inclusion.&lt;/p&gt;

&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better propagation speed&lt;/li&gt;
&lt;li&gt;Lower failure rates&lt;/li&gt;
&lt;li&gt;Improved validator routing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Jito Bundles
&lt;/h3&gt;

&lt;p&gt;Jito Labs provides transaction bundle infrastructure.&lt;/p&gt;

&lt;p&gt;This allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Priority ordering&lt;/li&gt;
&lt;li&gt;Better landing probability&lt;/li&gt;
&lt;li&gt;MEV-aware execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For launch sniping, this is a major edge.&lt;/p&gt;

&lt;p&gt;Auto Sell Logic&lt;/p&gt;

&lt;p&gt;Buying is easy.&lt;/p&gt;

&lt;p&gt;Selling well is difficult.&lt;/p&gt;

&lt;p&gt;The bot includes:&lt;/p&gt;

&lt;p&gt;Take Profit&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30%&lt;/li&gt;
&lt;li&gt;50%&lt;/li&gt;
&lt;li&gt;100%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bot continuously checks quote value against entry.&lt;/p&gt;

&lt;p&gt;If target is reached:&lt;/p&gt;

&lt;p&gt;Sell triggers automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop Loss
&lt;/h3&gt;

&lt;p&gt;Protects downside.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;-10%&lt;br&gt;
-20%&lt;/p&gt;

&lt;p&gt;This prevents catastrophic drawdowns.&lt;/p&gt;

&lt;p&gt;Especially useful in volatile low-cap launches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timed Exit
&lt;/h3&gt;

&lt;p&gt;Not every token pumps.&lt;/p&gt;

&lt;p&gt;Some die.&lt;/p&gt;

&lt;p&gt;The timed exit ensures capital rotation:&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Sell after 15 minutes regardless of performance.&lt;/p&gt;

&lt;p&gt;This improves opportunity efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Performance Bottlenecks I Faced
&lt;/h2&gt;

&lt;p&gt;While building this bot, I encountered several bottlenecks:&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket Flooding
&lt;/h3&gt;

&lt;p&gt;Too many market events caused memory pressure.&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event deduplication&lt;/li&gt;
&lt;li&gt;Listener cleanup&lt;/li&gt;
&lt;li&gt;Smart cache eviction&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RPC Rate Limits
&lt;/h3&gt;

&lt;p&gt;Public endpoints failed frequently.&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;Dedicated premium RPC providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failed Transactions
&lt;/h3&gt;

&lt;p&gt;Congestion caused dropped buys.&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry logic&lt;/li&gt;
&lt;li&gt;Priority fee tuning&lt;/li&gt;
&lt;li&gt;Warp/Jito execution&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Token Account Issues
&lt;/h3&gt;

&lt;p&gt;Many users forgot to initialize WSOL/USDC token accounts.&lt;/p&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;p&gt;Pre-check wallet balances before execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Model
&lt;/h2&gt;

&lt;p&gt;Security was non-negotiable.&lt;/p&gt;

&lt;p&gt;Private keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stay local&lt;/li&gt;
&lt;li&gt;Never leave the machine&lt;/li&gt;
&lt;li&gt;Never sent to Warp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even fee signing happens locally.&lt;/p&gt;

&lt;p&gt;Best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use burner wallets&lt;/li&gt;
&lt;li&gt;Limit active balances&lt;/li&gt;
&lt;li&gt;Use isolated infrastructure&lt;/li&gt;
&lt;li&gt;Rotate credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Is This Bot For?
&lt;/h2&gt;

&lt;p&gt;This project is useful for:&lt;/p&gt;

&lt;h3&gt;
  
  
  Traders
&lt;/h3&gt;

&lt;p&gt;Who want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast sniping&lt;/li&gt;
&lt;li&gt;Automated exits&lt;/li&gt;
&lt;li&gt;Risk filters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developers
&lt;/h3&gt;

&lt;p&gt;Who want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extend execution logic&lt;/li&gt;
&lt;li&gt;Add AI scoring systems&lt;/li&gt;
&lt;li&gt;Build arbitrage modules&lt;/li&gt;
&lt;li&gt;Add analytics pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Researchers
&lt;/h3&gt;

&lt;p&gt;Who want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Study token launch patterns&lt;/li&gt;
&lt;li&gt;Analyze rug probabilities&lt;/li&gt;
&lt;li&gt;Model memecoin volatility&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Open Source Repository
&lt;/h2&gt;

&lt;p&gt;The full source code is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/legendaryangelist/solana-trading-bot-v3" rel="noopener noreferrer"&gt;https://github.com/legendaryangelist/solana-trading-bot-v3&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;p&gt;✔ Real-time market monitoring&lt;br&gt;
✔ Multi-layer rug filters&lt;br&gt;
✔ Auto buy/sell&lt;br&gt;
✔ Warp execution&lt;br&gt;
✔ Jito support&lt;br&gt;
✔ Snipe list support&lt;br&gt;
✔ Configurable risk engine&lt;br&gt;
✔ Production-ready architecture&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a &lt;strong&gt;Solana trading bot&lt;/strong&gt; taught me something important:&lt;/p&gt;

&lt;p&gt;Winning in on-chain markets isn’t just about speed.&lt;/p&gt;

&lt;p&gt;It’s about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Information quality&lt;/li&gt;
&lt;li&gt;Risk filtering&lt;/li&gt;
&lt;li&gt;Execution reliability&lt;/li&gt;
&lt;li&gt;Capital discipline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation gives you speed.&lt;/p&gt;

&lt;p&gt;Infrastructure gives you consistency.&lt;/p&gt;

&lt;p&gt;Strategy gives you longevity.&lt;/p&gt;

&lt;p&gt;If you’re a developer exploring &lt;strong&gt;Solana bot development, automated trading systems&lt;/strong&gt;, or &lt;strong&gt;on-chain sniper infrastructure&lt;/strong&gt;, I hope this repository gives you a strong foundation.&lt;/p&gt;

&lt;p&gt;The market moves fast.&lt;/p&gt;

&lt;p&gt;Your code should move faster.&lt;/p&gt;

</description>
      <category>solana</category>
      <category>crypto</category>
      <category>trading</category>
      <category>bot</category>
    </item>
    <item>
      <title>"Read-Only Reviewer Agents Catch What Your Main Agent Waves Through"</title>
      <dc:creator>greymoth</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:51:19 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/greymothjp/read-only-reviewer-agents-catch-what-your-main-agent-waves-through-3ggc</link>
      <guid>https://dev.clauneck.workers.dev/greymothjp/read-only-reviewer-agents-catch-what-your-main-agent-waves-through-3ggc</guid>
      <description>&lt;p&gt;The main agent writes the code. The reviewer agent reads it. The reviewer can't edit anything.&lt;/p&gt;

&lt;p&gt;That constraint — no edit permission — is the whole point. It's not a safety measure. It's what makes the reviewer trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "waving through" looks like
&lt;/h2&gt;

&lt;p&gt;I noticed a pattern in my own agentic work. The main agent finishes a feature, the tests pass, I mark it done. Later I find something wrong — a null case not handled, an error message that doesn't match the spec, a behavior that passes the test but fails in a slightly different context. Things that were visible in the code. Things I should have caught.&lt;/p&gt;

&lt;p&gt;The main agent wrote the code and verified the code. By the time it's reading its own output, it has already mentally modeled the feature as complete. It reads with the assumption that things are correct and looks for confirmation. It finds confirmation, because it wrote the code to pass the test it also wrote.&lt;/p&gt;

&lt;p&gt;This is a verification problem, not an intelligence problem. The same issue shows up in human code review — the author is the worst person to catch their own oversights, not because they're careless but because they're too close to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tool scope is the right lever
&lt;/h2&gt;

&lt;p&gt;You could try to solve this with prompting. Tell the main agent to "review critically before finishing." I've tried it. It helps a little. The agent will surface some issues it would have let slide. But it's working against its own completion-bias, and that's a weak force.&lt;/p&gt;

&lt;p&gt;Scoping tools differently is a structural fix, not a nudge.&lt;/p&gt;

&lt;p&gt;A reviewer agent that literally cannot call any write or edit tool has a different relationship to the code. It's not trying to finish. There's nothing to finish. It reads the diff, reads the spec, reads the test results, and that's all it can do. Its job is to notice things, not to fix them.&lt;/p&gt;

&lt;p&gt;When you remove the option to fix, the noticing gets sharper.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reviewer actually catches
&lt;/h2&gt;

&lt;p&gt;In practice the reviewer finds a few categories of things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spec drift.&lt;/strong&gt; The main agent made a small judgment call somewhere that deviated from the spec. Not wrong exactly — a reasonable decision — but not what the spec says. The reviewer has the spec open and no stake in the implementation, so it flags it. The main agent might not have even noticed the deviation because the implementation "feels right."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing cases.&lt;/strong&gt; The main agent handled the happy path and the obvious error case. The reviewer asks: what happens if this input is empty? What if this call fails? These are findable by reading; they just require a different mode of attention than writing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test-implementation coupling.&lt;/strong&gt; Sometimes the test passes because the implementation and the test were written together, with the same blind spot. The reviewer reads both and asks whether the test actually covers the behavior, or just covers the code. These are different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminology mismatch.&lt;/strong&gt; Error messages, variable names, API response fields that don't match the spec or the existing codebase conventions. Small. Easy to miss when you're focused on logic. Easy to catch when you're only reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set this up
&lt;/h2&gt;

&lt;p&gt;The mechanics are simple. You're running two agent calls sequentially, not in parallel. The main agent does its work. When it's done, you pass the diff and the spec to a second agent with a system prompt that says it's a reviewer — and you configure it with read-only file access.&lt;/p&gt;

&lt;p&gt;In Claude Code specifically, you can scope tool permissions at the agent level. The reviewer gets &lt;code&gt;Read&lt;/code&gt;, &lt;code&gt;Grep&lt;/code&gt;, &lt;code&gt;Glob&lt;/code&gt;. No &lt;code&gt;Edit&lt;/code&gt;, no &lt;code&gt;Write&lt;/code&gt;, no &lt;code&gt;Bash&lt;/code&gt;. It literally can't change anything even if it wanted to.&lt;/p&gt;

&lt;p&gt;The reviewer's output is a list of findings. The main agent then gets those findings and addresses them. One more pass, one more reviewer check if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's not worth it
&lt;/h2&gt;

&lt;p&gt;Short scripts, throwaway code, things you'll rewrite anyway — the overhead isn't worth it. The reviewer adds latency and cost. For anything going to production, or anything that needs to match a spec, or anything you'll hand off: worth it.&lt;/p&gt;

&lt;p&gt;The other case where it adds real value is when the spec is load-bearing. If you've written precise acceptance criteria and you want to know whether the implementation actually meets them — not just "tests pass" but "the criteria are satisfied" — the reviewer is good at that comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deeper point
&lt;/h2&gt;

&lt;p&gt;The problem isn't that the main agent makes mistakes. It's that agent and reviewer are the same entity if they share context, state, and stake in the outcome.&lt;/p&gt;

&lt;p&gt;Separate the concerns. Give each agent exactly the tools its role requires, no more. The reviewer doesn't need to write because its job is to read. When it can only read, it reads better.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why Your Developer Productivity Metrics Are Measuring the Wrong Thing</title>
      <dc:creator>Ak Thoha</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:46:20 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/akthoha/why-your-developer-productivity-metrics-are-measuring-the-wrong-thing-3npl</link>
      <guid>https://dev.clauneck.workers.dev/akthoha/why-your-developer-productivity-metrics-are-measuring-the-wrong-thing-3npl</guid>
      <description>&lt;p&gt;Your DORA metrics are elite. Your velocity is up. Releases still slip.&lt;/p&gt;

&lt;p&gt;You're measuring the pipeline, not what's blocking it.&lt;/p&gt;

&lt;h2&gt;
  
  
  DORA Sees the End, Not the Wait
&lt;/h2&gt;

&lt;p&gt;DORA metrics are real. Google's research validates them. But they only see one moment: when code ships.&lt;/p&gt;

&lt;p&gt;They don't see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 6 days a feature sat waiting for security review&lt;/li&gt;
&lt;li&gt;The two teams building the same dependency, unaware of each other&lt;/li&gt;
&lt;li&gt;The Slack thread where the real blocking decision lives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DORA sees elite teams ship fast. It doesn't see why those same elite teams still slip releases while mediocre teams ship on time.&lt;/p&gt;

&lt;p&gt;The answer: visibility. One team can see its dependencies. The other can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Story Points Are a Confidence Game
&lt;/h2&gt;

&lt;p&gt;Ron Jeffries invented story points and now regrets it—not because estimation is bad, but because points became a proxy for productivity when they measure neither.&lt;/p&gt;

&lt;p&gt;Watch this pattern:&lt;/p&gt;

&lt;p&gt;Q1: 45 → 45. Aligned.&lt;br&gt;
Q2: 52 → 50. Velocity up.&lt;br&gt;
Q3: 58 → 54. Charts green.&lt;br&gt;
Reality: Same shipping speed all three quarters.&lt;/p&gt;

&lt;p&gt;Teams learned to estimate generously without changing output. It works great until you cross team boundaries and suddenly Team A's 5 points has nothing to do with Team B's 5 points.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPACE Found the Problem—Then Stopped
&lt;/h2&gt;

&lt;p&gt;GitHub and Microsoft's SPACE framework added communication as a dimension of productivity. Good instinct.&lt;/p&gt;

&lt;p&gt;Then it suggested measuring communication with surveys: "Do you feel informed?"&lt;/p&gt;

&lt;p&gt;That's a feeling, not a signal.&lt;/p&gt;

&lt;p&gt;The real signals hide in Slack threads nobody linked to tickets. PRs waiting on async reviews. Code shipped that silently broke another team's API contract.&lt;/p&gt;

&lt;p&gt;SPACE named the problem. It didn't solve the visibility problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Signals That Show You're Blind
&lt;/h2&gt;

&lt;p&gt;If you want to see what's actually slowing you down, measure this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Phantom Dependencies How many pieces of work touch code across multiple repos without a formal dependency ticket? Most teams discover these only after merge. Panic mode activated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Async Resolution Time Slack threads: 5-7 days to resolve blockers. Jira: a few hours. Same information, different container. This tells you something about where knowledge actually lives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Context-Switching Interrupts UC Irvine research: 23 minutes to regain focus after one interrupt. Count the "who owns this?" moments in one engineer's week. Multiply that across your team. That's real velocity drag.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;Layer 1: Flow Metrics (Keep DORA) Use cycle time, not velocity. Velocity is a number teams learn to adjust. Cycle time is measured time.&lt;/p&gt;

&lt;p&gt;Layer 2: Coordination Signals (The Missing Layer) Measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Untracked dependencies discovered post-merge per release&lt;/li&gt;
&lt;li&gt;How long cross-team blockers sit in async channels&lt;/li&gt;
&lt;li&gt;Pre-merge vs. post-merge dependency discovery rate&lt;/li&gt;
&lt;li&gt;Unplanned context switches per engineer per week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connect your tools and ask: "What's actually blocking work across all these systems?"&lt;/p&gt;

&lt;p&gt;Layer 3: Outcome Confidence (Forward-Looking) Turn vague status updates into risk forecasts. "We're on track" becomes "72% confidence, main risk is untracked payment team dependency."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;Try this on your last 3 releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blind spots: How many cross-team dependencies did you discover only after code shipped?&lt;/li&gt;
&lt;li&gt;Async channels: Which Slack threads contain blocking conversations? How long open?&lt;/li&gt;
&lt;li&gt;One team, one week: Count "who owns this?" moments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams find the numbers shocking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Question
&lt;/h2&gt;

&lt;p&gt;If releases slip despite strong metrics, you're measuring the wrong things.&lt;/p&gt;

&lt;p&gt;Not "are developers fast?" but "can we see what's blocking them?"&lt;/p&gt;

&lt;p&gt;Teams that surface dependency risk early ship 30-40% faster. Not because they code faster—because they can see.&lt;/p&gt;

&lt;p&gt;Start measuring handoffs.&lt;/p&gt;

&lt;p&gt;Mythreyi Chandoor builds execution intelligence tooling. 15+ years as a technical program director watching smart teams ship slower than they should—usually because visibility stopped at team boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;What's the biggest coordination blocker you see in your engineering org? Where do you lose the most time in handoffs nobody tracks?&lt;/p&gt;

&lt;p&gt;Drop your experience in the comments—genuinely curious how others are solving this.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devops</category>
      <category>engineering</category>
      <category>metrics</category>
    </item>
    <item>
      <title>Loop Engineering: Why Prompt Engineering Is Becoming Obsolete</title>
      <dc:creator>luka</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:45:56 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/pebira/loop-engineering-why-prompt-engineering-is-becoming-obsolete-58oj</link>
      <guid>https://dev.clauneck.workers.dev/pebira/loop-engineering-why-prompt-engineering-is-becoming-obsolete-58oj</guid>
      <description>&lt;p&gt;For the last two years, "Prompt Engineering" has been one of the hottest skills in AI.&lt;/p&gt;

&lt;p&gt;People spent countless hours optimizing prompts:&lt;/p&gt;

&lt;p&gt;Add more context.&lt;br&gt;
Use chain of thought.&lt;br&gt;
Ask the model to think step by step.&lt;br&gt;
Create reusable prompt templates.&lt;/p&gt;

&lt;p&gt;Entire courses, books, and careers emerged around writing the perfect prompt.&lt;/p&gt;

&lt;p&gt;But something fundamental has changed.&lt;/p&gt;

&lt;p&gt;The future isn't about writing better prompts.&lt;/p&gt;

&lt;p&gt;It's about building better loops.&lt;/p&gt;

&lt;p&gt;Prompt Engineering Assumes One Conversation&lt;/p&gt;

&lt;p&gt;Traditional prompt engineering treats every interaction as an isolated event.&lt;/p&gt;

&lt;p&gt;Human&lt;br&gt;
    ↓&lt;br&gt;
Prompt&lt;br&gt;
    ↓&lt;br&gt;
LLM&lt;br&gt;
    ↓&lt;br&gt;
Answer&lt;/p&gt;

&lt;p&gt;If the answer isn't good enough, you rewrite the prompt.&lt;/p&gt;

&lt;p&gt;The prompt becomes the product.&lt;/p&gt;

&lt;p&gt;This made sense when LLMs were essentially advanced autocomplete systems.&lt;/p&gt;

&lt;p&gt;Modern AI Doesn't Stop After One Response&lt;/p&gt;

&lt;p&gt;Today's AI agents don't simply answer.&lt;/p&gt;

&lt;p&gt;They observe.&lt;/p&gt;

&lt;p&gt;They execute.&lt;/p&gt;

&lt;p&gt;They evaluate.&lt;/p&gt;

&lt;p&gt;They retry.&lt;/p&gt;

&lt;p&gt;A coding agent might:&lt;/p&gt;

&lt;p&gt;read your repository&lt;br&gt;
generate code&lt;br&gt;
run tests&lt;br&gt;
discover failures&lt;br&gt;
modify the implementation&lt;br&gt;
rerun tests&lt;br&gt;
repeat until success&lt;/p&gt;

&lt;p&gt;The original prompt quickly becomes irrelevant.&lt;/p&gt;

&lt;p&gt;What matters is the feedback loop.&lt;/p&gt;

&lt;p&gt;The Prompt Is Now Just Initialization&lt;/p&gt;

&lt;p&gt;Think about developers using Claude Code, Codex, Gemini CLI, or OpenHands.&lt;/p&gt;

&lt;p&gt;The initial instruction might be:&lt;/p&gt;

&lt;p&gt;"Implement dark mode."&lt;/p&gt;

&lt;p&gt;Everything afterward happens inside an iterative process.&lt;/p&gt;

&lt;p&gt;The agent continuously gathers new information.&lt;/p&gt;

&lt;p&gt;It edits files.&lt;/p&gt;

&lt;p&gt;Reads compiler errors.&lt;/p&gt;

&lt;p&gt;Checks logs.&lt;/p&gt;

&lt;p&gt;Runs commands.&lt;/p&gt;

&lt;p&gt;Refines its plan.&lt;/p&gt;

&lt;p&gt;Eventually, the original prompt becomes a tiny fraction of the total reasoning process.&lt;/p&gt;

&lt;p&gt;Enter Loop Engineering&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;How do I write the perfect prompt?&lt;/p&gt;

&lt;p&gt;We should ask:&lt;/p&gt;

&lt;p&gt;How do I design the perfect iteration loop?&lt;/p&gt;

&lt;p&gt;A good AI loop includes:&lt;/p&gt;

&lt;p&gt;memory&lt;br&gt;
verification&lt;br&gt;
tool execution&lt;br&gt;
feedback&lt;br&gt;
retry strategy&lt;br&gt;
stopping conditions&lt;br&gt;
evaluation&lt;/p&gt;

&lt;p&gt;The intelligence increasingly comes from the loop—not the prompt.&lt;/p&gt;

&lt;p&gt;An Example&lt;/p&gt;

&lt;p&gt;Bad approach:&lt;/p&gt;

&lt;p&gt;Write the perfect prompt.&lt;br&gt;
Hope it works.&lt;/p&gt;

&lt;p&gt;Loop Engineering:&lt;/p&gt;

&lt;p&gt;Generate solution&lt;br&gt;
        ↓&lt;br&gt;
Execute&lt;br&gt;
        ↓&lt;br&gt;
Measure&lt;br&gt;
        ↓&lt;br&gt;
Identify failure&lt;br&gt;
        ↓&lt;br&gt;
Improve&lt;br&gt;
        ↓&lt;br&gt;
Repeat&lt;/p&gt;

&lt;p&gt;Notice what's happening.&lt;/p&gt;

&lt;p&gt;The model no longer depends on humans to refine the prompt.&lt;/p&gt;

&lt;p&gt;The system refines itself.&lt;/p&gt;

&lt;p&gt;This Is How Human Experts Work&lt;/p&gt;

&lt;p&gt;Software engineers don't write perfect code on the first try.&lt;/p&gt;

&lt;p&gt;Scientists don't publish their first hypothesis.&lt;/p&gt;

&lt;p&gt;Designers don't ship their first sketch.&lt;/p&gt;

&lt;p&gt;Experts iterate.&lt;/p&gt;

&lt;p&gt;AI is moving toward the same workflow.&lt;/p&gt;

&lt;p&gt;Iteration is replacing instruction.&lt;/p&gt;

&lt;p&gt;The Real Competitive Advantage&lt;/p&gt;

&lt;p&gt;Many people still ask:&lt;/p&gt;

&lt;p&gt;"What's the best prompt?"&lt;/p&gt;

&lt;p&gt;Increasingly, that's the wrong question.&lt;/p&gt;

&lt;p&gt;The better questions are:&lt;/p&gt;

&lt;p&gt;How does the agent detect failure?&lt;br&gt;
How does it recover?&lt;br&gt;
What information should persist between iterations?&lt;br&gt;
When should it stop?&lt;br&gt;
Which tools should it call next?&lt;/p&gt;

&lt;p&gt;These are Loop Engineering problems.&lt;/p&gt;

&lt;p&gt;Prompt Engineers May Become Loop Engineers&lt;/p&gt;

&lt;p&gt;Prompt engineering isn't disappearing overnight.&lt;/p&gt;

&lt;p&gt;A good initial prompt still matters.&lt;/p&gt;

&lt;p&gt;But its importance is shrinking.&lt;/p&gt;

&lt;p&gt;As AI gains longer context windows, persistent memory, tool use, and autonomous execution, the prompt becomes merely the starting state of a much larger system.&lt;/p&gt;

&lt;p&gt;The real engineering challenge shifts from language to process.&lt;/p&gt;

&lt;p&gt;From wording to workflows.&lt;/p&gt;

&lt;p&gt;From prompts to loops.&lt;/p&gt;

&lt;p&gt;And perhaps, in a few years, we'll look back at "Prompt Engineer" the same way we look back at "Flash Developer" or "SEO Meta Keywords Specialist"—an important role for its era, but one eventually absorbed into a broader discipline.&lt;/p&gt;

&lt;p&gt;Maybe the next generation won't call themselves Prompt Engineers.&lt;/p&gt;

&lt;p&gt;Maybe they'll simply be Loop Engineers.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚀 I Built PDFEditDesk – A Free Online PDF Toolkit 📄</title>
      <dc:creator>Rajkumar V</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:45:32 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/rajkumar30598/i-built-pdfeditdesk-a-free-online-pdf-toolkit-6p6</link>
      <guid>https://dev.clauneck.workers.dev/rajkumar30598/i-built-pdfeditdesk-a-free-online-pdf-toolkit-6p6</guid>
      <description>&lt;p&gt;👋 Hi Dev Community!&lt;/p&gt;

&lt;p&gt;I'm excited to share PDFEditDesk, a side project I've been building to make working with PDFs faster and easier.&lt;/p&gt;

&lt;p&gt;🌐 Try it here: &lt;a href="https://pdfeditdesk.com" rel="noopener noreferrer"&gt;https://pdfeditdesk.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ With PDFEditDesk, you can:&lt;/p&gt;

&lt;p&gt;📄 Edit PDFs&lt;br&gt;
🔗 Merge PDFs&lt;br&gt;
✂️ Split PDFs&lt;br&gt;
🗜️ Compress PDFs&lt;br&gt;
🔄 Convert PDFs&lt;br&gt;
📑 Organize PDF pages&lt;/p&gt;

&lt;p&gt;🎯 My goal was to create a clean, fast, and user-friendly experience without unnecessary complexity.&lt;/p&gt;

&lt;p&gt;I'd love your feedback on:&lt;/p&gt;

&lt;p&gt;🎨 UI/UX&lt;br&gt;
⚡ Performance&lt;br&gt;
🐞 Bugs&lt;br&gt;
💡 Features you'd like to see&lt;/p&gt;

&lt;p&gt;Every suggestion helps improve the product. Thanks for checking it out! ❤️🚀&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>"Your Repo Is the Memory. Your Model Is the Worker."</title>
      <dc:creator>greymoth</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:42:52 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/greymothjp/your-repo-is-the-memory-your-model-is-the-worker-3e09</link>
      <guid>https://dev.clauneck.workers.dev/greymothjp/your-repo-is-the-memory-your-model-is-the-worker-3e09</guid>
      <description>&lt;p&gt;There's a framing I keep coming back to. The model doesn't hold state — the repo does. The model is the worker. The repo is the memory. Once that clicks, a bunch of decisions about how to structure agentic work become obvious.&lt;/p&gt;

&lt;p&gt;I've been running Claude Code nearly every day. My token breakdown: cacheRead 72%, input 0.3%, output the rest. That 72% number isn't an accident. It's what happens when you treat the repo as the ground truth and stop asking the model to reconstruct context from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic principle
&lt;/h2&gt;

&lt;p&gt;The model forgets. Not sometimes — always, between sessions, and often within them when context gets long. If your workflow relies on the model remembering what it decided two hours ago, you're building on sand.&lt;/p&gt;

&lt;p&gt;The repo doesn't forget. A spec file, a requirements doc, a TODO list with completed items marked — these are durable. The model can read them fresh every time and pick up exactly where things are.&lt;/p&gt;

&lt;p&gt;So the workflow becomes: write everything down in the repo, have the model read before it acts, have the model write its decisions back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "frozen spec" means in practice
&lt;/h2&gt;

&lt;p&gt;The spec file is the source of truth. It's not a suggestion. When I start a work session I don't say "continue where we left off." I say "read the spec, read the current state of the repo, tell me what's left."&lt;/p&gt;

&lt;p&gt;The model reads. The model acts on what it reads. The model marks items done only after I've verified they work — not when it thinks it's done, not when it says it's done. After I've confirmed.&lt;/p&gt;

&lt;p&gt;This sounds slow. It's the opposite. I've burned hours debugging half-finished features because a previous session "completed" something that wasn't complete. The &lt;code&gt;[●]&lt;/code&gt; only goes in after verification. Every time I've skipped that, I've paid for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why cacheRead 72% matters
&lt;/h2&gt;

&lt;p&gt;When the model reads the same files repeatedly across a session — the spec, the main source files, the test results — those reads get served from cache. The cost difference between input tokens and cached reads is significant. More importantly, the latency drops. Reads that would have taken seconds are near-instant.&lt;/p&gt;

&lt;p&gt;The implication: structure your repo so the model reads the same files often. One clean spec file is better than scattered notes. One consolidated state file is better than comments spread across six modules. Predictable read patterns = high cache hit rate = faster, cheaper sessions.&lt;/p&gt;

&lt;p&gt;My 0.3% input figure is low because I'm not pasting long context into prompts. The repo provides the context. The prompt is just the directive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks this pattern
&lt;/h2&gt;

&lt;p&gt;Two things reliably break it.&lt;/p&gt;

&lt;p&gt;First: specs that change without the model knowing. If I update the requirements doc mid-session without telling the model to re-read it, the model is now operating on stale information it thinks is current. The fix is trivial — tell it to read the spec again — but easy to forget.&lt;/p&gt;

&lt;p&gt;Second: verification that isn't real verification. "Does this look right?" is not verification. Running the tests and reading the output is verification. Clicking through the UI and confirming behavior is verification. The &lt;code&gt;[●]&lt;/code&gt; gate only holds if the criterion is external and objective.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reviewer pattern
&lt;/h2&gt;

&lt;p&gt;One extension of this I've been using: a second model pass that's read-only. After the main agent makes changes, a reviewer agent reads the diff and the spec together, with no ability to edit files. It's surprising how often the reviewer catches things the main agent waved through — not because the main agent is bad, but because the same agent that wrote the code has already mentally "completed" the task and reads its own output charitably.&lt;/p&gt;

&lt;p&gt;The reviewer has no such prior. It reads what's there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes about how I write specs
&lt;/h2&gt;

&lt;p&gt;Short and precise beats long and thorough. A 200-line spec that's been carefully maintained is more useful than a 2,000-line doc that's 70% stale. The model will read the whole thing every time; every line of noise is a tax.&lt;/p&gt;

&lt;p&gt;I also stopped writing "what" specs and started writing "what + acceptance criterion" specs. Not "implement pagination" but "implement pagination — verified when /api/posts?page=2 returns the correct slice and the total count header is accurate." The acceptance criterion is what tells me when to mark &lt;code&gt;[●]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit
&lt;/h2&gt;

&lt;p&gt;This framing doesn't fix bad judgment. If the spec is wrong, the model faithfully executes the wrong thing. If the acceptance criterion is too easy to satisfy, you get implementations that pass the test and miss the intent.&lt;/p&gt;

&lt;p&gt;The model is a good worker. It does what it's told, reads what you give it, marks things done when you say so. Whether the work was the right work — that part is still yours.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>productivity</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>"I Found ~30 Mergeable OSS Bugs in a Day. They Were All the Same Shape."</title>
      <dc:creator>greymoth</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:42:48 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/greymothjp/i-found-30-mergeable-oss-bugs-in-a-day-they-were-all-the-same-shape-5c86</link>
      <guid>https://dev.clauneck.workers.dev/greymothjp/i-found-30-mergeable-oss-bugs-in-a-day-they-were-all-the-same-shape-5c86</guid>
      <description>&lt;p&gt;Yesterday I shipped around 28 pull requests across real repos — zod, NestJS, Fastify, Scrapy, Pygments, others. Not spam. Not docs typos. Actual behavioral fixes that got merged or are pending review.&lt;/p&gt;

&lt;p&gt;The method has a name now. I'm calling it &lt;strong&gt;sibling-leftover&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What sibling-leftover actually is
&lt;/h2&gt;

&lt;p&gt;When a maintainer merges a PR that fixes something in one area, they're implicitly approving the intent. But if the fix touched only one of two symmetric branches in the code — and left the sibling untouched — there's a window. The maintainer already agreed the behavior was wrong. The fix pattern is already in-tree. You're not arguing a new position; you're completing a sentence they started.&lt;/p&gt;

&lt;p&gt;The leftover sibling is almost always a bug. Not always a serious one. But it's real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two concrete examples
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;zod #5945 → #6141&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;zod #5945 fixed &lt;code&gt;cidrv4&lt;/code&gt; validation. The PR landed. Closed. Merged. I looked at the diff and noticed &lt;code&gt;cidrv6&lt;/code&gt; sat right next to it in the same file, same logic shape, no corresponding fix. Opened #6141 targeting &lt;code&gt;ipv6&lt;/code&gt;. The maintainer already understood the problem domain; the test pattern was already established. Merge friction: near zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NestJS #17188 → #17207&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NestJS #17188 fixed a behavior in the WebSockets transport. Adjacent in the codebase: the microservices transport, same underlying issue. #17207. Same shape, different branch. I didn't have to explain why something was wrong — the merged PR did that for me.&lt;/p&gt;

&lt;p&gt;This is the pattern. One fix → locate its symmetric counterpart → ship the mirror.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why merge rates are high
&lt;/h2&gt;

&lt;p&gt;Three reasons stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Domain approval is already in.&lt;/strong&gt; The maintainer accepted that this class of bug exists. They didn't reject the premise, they fixed it. You're not asking them to believe something new.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test infrastructure exists.&lt;/strong&gt; The first PR almost always adds or modifies tests. Your fix can follow the same structure, in the same file, targeting the sibling case. Reviewers don't have to imagine what passing looks like.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The diff is small and symmetric.&lt;/strong&gt; A small, obviously correct diff in a familiar area is much easier to approve than a large refactor. Cognitive load for the reviewer is low.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to run this yourself
&lt;/h2&gt;

&lt;p&gt;Step one: find recently merged PRs in repos you care about. GitHub search — &lt;code&gt;is:pr is:merged sort:updated&lt;/code&gt; — works. Filter by the last 30-60 days.&lt;/p&gt;

&lt;p&gt;Step two: read the diff. Look for constants, enum branches, transport names, protocol variants, format handlers — anything where the code obviously has siblings. &lt;code&gt;cidrv4&lt;/code&gt;/&lt;code&gt;cidrv6&lt;/code&gt;. &lt;code&gt;websockets&lt;/code&gt;/&lt;code&gt;microservices&lt;/code&gt;. &lt;code&gt;rgb&lt;/code&gt;/&lt;code&gt;rgba&lt;/code&gt;. &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https&lt;/code&gt;. These pairs are everywhere.&lt;/p&gt;

&lt;p&gt;Step three: grep the repo for the sibling. Confirm it has the same bug pattern. Write the fix following the same style as the merged PR — same variable naming, same test shape.&lt;/p&gt;

&lt;p&gt;Step four: open the PR. Reference the original merged fix explicitly. "This mirrors #XXXX which fixed the same issue in [sibling]. Applying the same approach here." That sentence alone cuts review time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;It's not cherry-picking trivially obvious things to pad a contribution count. Some of the fixes I found were non-trivial — the sibling had slightly different logic that required careful adaptation. A few I dropped because the behavior divergence was intentional and I wasn't sure.&lt;/p&gt;

&lt;p&gt;The method is a search strategy, not a guarantee. You still have to read the code. You still have to understand why the original fix was correct. The frame just tells you where to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing I'll keep doing
&lt;/h2&gt;

&lt;p&gt;Before I look for new bug categories, I now scan the merged PRs from the last week in repos I'm watching. It's maybe 20 minutes. The hit rate — "this fix has an unaddressed sibling" — is surprisingly high. My rough estimate from yesterday: about 40% of format/transport/protocol-related fixes leave something behind.&lt;/p&gt;

&lt;p&gt;The bugs are there. They're findable. The method is repeatable.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>productivity</category>
      <category>claudecode</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automating Student Onboarding via WhatsApp in SugarCRM</title>
      <dc:creator>Northbeam Technologies</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:41:34 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/northbeamtech/automating-student-onboarding-via-whatsapp-in-sugarcrm-pad</link>
      <guid>https://dev.clauneck.workers.dev/northbeamtech/automating-student-onboarding-via-whatsapp-in-sugarcrm-pad</guid>
      <description>&lt;p&gt;A multi-step conversation engine that captures leads and creates CRM records automatically — no staff required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When prospective students first contact the school via WhatsApp, staff had to manually collect their details and create a record in SugarCRM by hand. Leads that arrived outside business hours were simply missed or delayed — there was no way to capture them without a person in the loop.&lt;/p&gt;

&lt;p&gt;The school was already using Twilio for outbound WhatsApp messages from SugarCRM, but replies from unknown numbers went nowhere.&lt;/p&gt;

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

&lt;p&gt;A &lt;strong&gt;multi-step conversation engine&lt;/strong&gt; built directly into SugarCRM. When an unknown number sends a WhatsApp message, the system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Intercepts the message via a Twilio webhook&lt;/li&gt;
&lt;li&gt;Checks whether the phone number already exists as a student record&lt;/li&gt;
&lt;li&gt;If yes → routes to the existing follow-up flow (no change)&lt;/li&gt;
&lt;li&gt;If no → starts a guided intake questionnaire, validates each answer, and creates the student record automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero admin involvement for new contacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F438sbz766t5jvjxxpy3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F438sbz766t5jvjxxpy3o.png" alt=" " width="760" height="1120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Technical Decisions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;State machine, not a script.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Each conversation is a session with a &lt;code&gt;current_step&lt;/code&gt; pointer. Questions can be reordered, toggled, or mapped to any CRM field without changing code. Admins manage the question bank directly from SugarCRM's UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phone number normalization.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The system normalizes Israeli, US, and Vietnamese number formats before matching — preventing duplicate records from format variations like &lt;code&gt;+1-555...&lt;/code&gt; vs &lt;code&gt;001555...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Media guard.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If a student sends a photo or voice note, the system replies politely that only text is supported and automatically resends the current question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial completion handling.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If a student stops mid-flow, automated reminders go out at 2h and 24h. If minimum data (name + phone) is collected and the student still doesn't respond, a partial record is saved so the lead isn't lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-number support.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Multiple Twilio sender numbers can be registered in SugarCRM. Each conversation session locks to the number the student originally contacted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed After Launch
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Student record creation from WhatsApp is fully automated for new contacts&lt;/li&gt;
&lt;li&gt;✅ Leads outside business hours are captured without any staff intervention&lt;/li&gt;
&lt;li&gt;✅ Admins can view full chat history, manually resume sessions, or override any step from inside SugarCRM&lt;/li&gt;
&lt;li&gt;✅ The existing outbound messaging workflow for known students is completely unchanged&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHP&lt;/strong&gt; — core logic and SugarCRM custom modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SugarCRM&lt;/strong&gt; — custom modules for session management, question bank, and student records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twilio WhatsApp API&lt;/strong&gt; — inbound webhook + outbound messaging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; — session state persistence&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The main lesson here: WhatsApp bots don't need a separate platform. If your CRM already has a webhook endpoint and an API, you can build the conversation engine right inside it — and keep everything in one place for your team.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://northbeam-tech.com/whatsapp-student-onboarding-sugarcrm/" rel="noopener noreferrer"&gt;northbeam-tech.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>crm</category>
      <category>twilio</category>
      <category>automation</category>
    </item>
    <item>
      <title>Understanding Filament themes in v4/v5: from Colors to custom CSS</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:41:24 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/filamentmastery/understanding-filament-themes-in-v4v5-from-colors-to-custom-css-57a5</link>
      <guid>https://dev.clauneck.workers.dev/filamentmastery/understanding-filament-themes-in-v4v5-from-colors-to-custom-css-57a5</guid>
      <description>&lt;p&gt;Filament provides a flexible theming system that lets you customize the look and feel of your admin panel. This guide covers how to create and apply custom themes for Filament v4/v5 with Tailwind CSS v4.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an updated version of an earlier guide written for &lt;a href="https://filamentmastery.com/articles/customize-your-filament-panel-theme/" rel="noopener noreferrer"&gt;Filament v3 and Tailwind v3&lt;/a&gt;. The overall approach is similar, but what the theme command generates and the CSS syntax have changed with Tailwind v4's CSS-first configuration. If you're still on Filament v3, the original guide is available here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting primary colors for panels
&lt;/h2&gt;

&lt;p&gt;Before touching the theme files, the simplest customization is setting your panel's color palette. This part hasn't changed: Filament still uses the &lt;code&gt;Filament\Support\Colors\Color&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: define colors per panel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your panel provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Support\Colors\Color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Panel&lt;/span&gt; &lt;span class="nv"&gt;$panel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Panel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$panel&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'danger'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'gray'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'info'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'primary'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Indigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Emerald&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'warning'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Orange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: define colors globally&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;AppServiceProvider&lt;/code&gt;, applied across all panels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Facades\Filament&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Support\Colors\Color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Filament&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;defaultColors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'danger'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'gray'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'info'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'primary'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Indigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Emerald&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'warning'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Orange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to stop here:&lt;/strong&gt;  if your goal is only to change branding colors, &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; is all you need. Create a custom theme only when you need structural UI changes, like repositioning elements, changing spacing, or styling parts of the interface that color tokens don't reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the theme
&lt;/h2&gt;

&lt;p&gt;The command itself hasn't changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-theme

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for a panel name (default: &lt;code&gt;admin&lt;/code&gt;). What changed is what this command generates and does for you.&lt;/p&gt;

&lt;p&gt;On a Tailwind v4 setup, running it for a &lt;code&gt;member&lt;/code&gt; panel generates a theme.css scoped to that panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'../../../../vendor/filament/filament/resources/css/theme.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/Member/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/member/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;@source&lt;/code&gt; paths point specifically to &lt;code&gt;Member&lt;/code&gt;, not a generic  &lt;code&gt;Filament/**/*&lt;/code&gt;. Each panel gets its own scoped theme out of the box.&lt;/p&gt;

&lt;p&gt;That's it: the generated file is intentionally minimal. If you want a dedicated place for CSS variables or light/dark overrides, you can add your own &lt;code&gt;@layer base&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other change worth noting: in Filament v3, you had to manually add the theme to &lt;code&gt;vite.config.js&lt;/code&gt; and register it with &lt;code&gt;-&amp;gt;viteTheme()&lt;/code&gt; in your panel provider. In v4/v5, the command does both for you automatically. No manual wiring required.&lt;/p&gt;

&lt;p&gt;A few things changed compared to Tailwind v3:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No more &lt;code&gt;tailwind.config.js&lt;/code&gt;.&lt;/strong&gt;  Tailwind v4 no longer requires a &lt;strong&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt;&lt;/strong&gt; file for basic theme customization. It moved to CSS-first configuration. There's nothing left to generate or maintain in a separate JS config file for the theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@source&lt;/code&gt; directives replace the old &lt;code&gt;content&lt;/code&gt; array.&lt;/strong&gt;  Tailwind v4 scans these paths directly from CSS to know which files to look at for class usage, instead of declaring them in a JS config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The generated file is intentionally minimal.&lt;/strong&gt;  Just the import and the &lt;code&gt;@source&lt;/code&gt; paths for that panel. No boilerplate &lt;code&gt;@layer base&lt;/code&gt; block is added automatically; that's something you add yourself if and when you need it, as shown above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your own directories to &lt;a class="mentioned-user" href="https://dev.clauneck.workers.dev/source"&gt;@source&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The generated &lt;code&gt;@source&lt;/code&gt; directives only cover Filament's own files. Your resources, components, or anything outside &lt;code&gt;app/Filament&lt;/code&gt; won't be scanned by default. If you use Tailwind classes in Blade components, Livewire components, or custom views, you need to add those directories yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/components/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/livewire/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Livewire/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forget to add a directory here, and Tailwind will purge classes it never saw being used, even if they're correctly written in your code. Rebuild with &lt;code&gt;npm run build&lt;/code&gt; after adding new paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets registered automatically
&lt;/h2&gt;

&lt;p&gt;Since the command handles registration for you, &lt;code&gt;vite.config.js&lt;/code&gt; already includes the new theme after running &lt;code&gt;make:filament-theme&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;laravel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;laravel-vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/css/app.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/css/filament/admin/theme.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your panel provider already has the theme registered too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/admin/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing left to wire up manually. Just run the command, and both files are updated for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple panels, multiple themes
&lt;/h2&gt;

&lt;p&gt;If your application has more than one panel, an admin panel and a member panel for example, each one gets its own theme file and is registered independently.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;make:filament-theme&lt;/code&gt; once per panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-theme backend
php artisan make:filament-theme member

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run automatically adds its theme to &lt;code&gt;vite.config.js&lt;/code&gt; and registers it in the matching panel provider. You'll end up with both entries already in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/app.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/filament/backend/theme.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/filament/member/theme.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/js/app.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;


&lt;span class="c1"&gt;// BackendPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'backend'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/backend/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// MemberPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'member'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/member/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;theme.css&lt;/code&gt; is scoped to its own panel by default. The &lt;code&gt;@source&lt;/code&gt; directives only point to that panel's files, and customizing one theme doesn't affect the other. This is how the &lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multipanel&lt;/a&gt; and &lt;a href="https://filamentmastery.com/articles/laravel-filament-multi-tenant-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multi-Tenant&lt;/a&gt; starters are set up: two themes, generated empty and scoped, ready for each panel to be styled differently if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing one theme across panels:&lt;/strong&gt;  you don't have to generate a separate file per panel. If two panels should look identical, you can point both panel providers at the same theme file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BackendPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/shared/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// MemberPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/shared/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just make sure the &lt;code&gt;@source&lt;/code&gt; paths in that shared file cover both panels directories, since a single generated theme only scopes to the panel it was created for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Filament's hook classes
&lt;/h2&gt;

&lt;p&gt;Once you start customizing beyond colors, you'll be targeting &lt;a href="https://filamentphp.com/docs/5.x/styling/css-hooks?ref=filamentmastery.com#common-hook-class-abbreviations" rel="noopener noreferrer"&gt;Filament's own CSS classes&lt;/a&gt;, the ones used in the sidebar example below and throughout the interface. They follow a consistent naming convention worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fi&lt;/code&gt; is short for "Filament"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-ac&lt;/code&gt; is used for classes from the Actions package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-fo&lt;/code&gt; is used for classes from the Forms package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-in&lt;/code&gt; is used for classes from the Infolists package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-no&lt;/code&gt; is used for classes from the Notifications package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-sc&lt;/code&gt; is used for classes from the Schema package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-ta&lt;/code&gt; is used for classes from the Tables package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-wi&lt;/code&gt; is used for classes from the Widgets package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;btn&lt;/code&gt; is short for "button"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;col&lt;/code&gt; is short for "column"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctn&lt;/code&gt; is short for "container"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wrp&lt;/code&gt; is short for "wrapper"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you know this pattern, hook class names become much easier to read without digging through Filament's source every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding hook classes in practice:&lt;/strong&gt;  the naming convention tells you what a class means once you've found it, but you'll still need to find it first. The fastest way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your browser's dev tools on the panel you want to customize&lt;/li&gt;
&lt;li&gt;Inspect the element you want to target&lt;/li&gt;
&lt;li&gt;Look for classes starting with &lt;code&gt;fi-&lt;/code&gt; in the inspector&lt;/li&gt;
&lt;li&gt;Use that class in your theme's CSS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the most reliable way to target the exact element you're looking at, rather than guessing from the naming convention alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing your theme
&lt;/h2&gt;

&lt;p&gt;With the theme registered, you can now add your own styles directly in &lt;code&gt;theme.css&lt;/code&gt;. Filament's hook classes, documented in the &lt;a href="https://filamentphp.com/docs/5.x/styling/overview?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Filament docs&lt;/a&gt;, let you target specific parts of the interface.&lt;/p&gt;

&lt;p&gt;Here's an example customizing the sidebar, the same idea as in the original v3 guide, adapted to the new file structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'../../../../vendor/filament/filament/resources/css/theme.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;.fi-sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;!bg-gray-500;&lt;/span&gt;

    &lt;span class="err"&gt;.fi-sidebar-header&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;.fi-icon-btn-icon&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-primary-500;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-group-label&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-icon-btn-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-group-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-button&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-white;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-item-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;.fi-sidebar-item-button&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-primary-500;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-item&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bg-primary-500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-icon-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text-primary-500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-nav-groups&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;gap-y-0;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@apply&lt;/code&gt; syntax and the hook classes themselves haven't changed. What changed is everything around them: how the theme is generated, how Tailwind scans your files, and how the build is configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using CSS variables for reusable values
&lt;/h3&gt;

&lt;p&gt;If you find yourself repeating the same color or value across multiple rules, defining a CSS variable in &lt;code&gt;@layer base&lt;/code&gt; keeps things consistent and easier to update later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="py"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1f2937&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="py"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference it directly in your styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.fi-sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful for values you'll reuse in several places, or want to swap between light and dark mode without duplicating rules.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm run build&lt;/code&gt; once you're done, and your custom theme is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when customizing Filament themes
&lt;/h2&gt;

&lt;p&gt;A few things come up repeatedly when developers start working with Filament themes for the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a theme when &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; is enough.&lt;/strong&gt; If the only goal is changing the primary color, buttons, and sidebar accents, &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; in the panel provider covers that without generating any files. A custom theme is only necessary for structural changes, targeting specific interface elements via hook classes, or using CSS variables for fine-grained control. Generating a theme file when it isn't needed just adds a build step and a file to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to add custom Blade directories to &lt;code&gt;@source&lt;/code&gt;.&lt;/strong&gt;  The generated theme only scans Filament's own directories by default. Any Tailwind classes used in custom Blade components, Livewire components, or views outside &lt;code&gt;app/Filament&lt;/code&gt; won't be detected unless you explicitly add those paths. Tailwind silently purges the undetected classes, and the issue only shows up at build time when styles randomly stop working in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Styling internal Filament classes instead of hook classes.&lt;/strong&gt;  Filament's hook classes, prefixed with &lt;code&gt;fi-&lt;/code&gt;, are the intended surface for CSS customization. Styling classes without the &lt;code&gt;fi-&lt;/code&gt; prefix means targeting internal implementation details that can change between releases without notice. Filament's own documentation explicitly warns against this: if a hook class you need doesn't exist yet, the recommended path is to open a pull request to add it, not to target internal classes and accept the maintenance risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overriding too much.&lt;/strong&gt;  The more CSS overrides added to a theme, the harder upgrades become. When Filament ships a new version, any structural change to the interface can break rules that were written against the old markup. Prefer color tokens, CSS variables, and targeted hook class overrides before rewriting large sections of the interface. A theme should adapt Filament to a brand, not turn Filament into a completely different application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And of course, editing vendor files directly.&lt;/strong&gt;  Any change made inside &lt;code&gt;vendor/filament&lt;/code&gt; is overwritten the next time &lt;code&gt;composer update&lt;/code&gt; runs. If something in Filament's default theme needs to be overridden, the right place is the custom &lt;code&gt;theme.css&lt;/code&gt; file, using hook classes to target the element. The vendor directory is never the right place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't want to build a theme from scratch?
&lt;/h3&gt;

&lt;p&gt;If building a custom Filament theme isn't where you want to spend your time, Filafly offers premium themes and plugins ready to drop into your panel.&lt;/p&gt;

&lt;p&gt;Use code &lt;strong&gt;MASTERY15&lt;/strong&gt; for &lt;strong&gt;15% off your purchase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://filafly.com/?utm_source=filamentmastery&amp;amp;utm_medium=affiliate&amp;amp;utm_campaign=affiliates&amp;amp;ref=filamentmastery" rel="noopener noreferrer"&gt;Discover Filafly themes →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>styling</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>The No-Panic Guide to JSON Schema Validation for REST APIs</title>
      <dc:creator>Janardan Joshi</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:38:21 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/janardan_joshi_6f21026e06/the-no-panic-guide-to-json-schema-validation-for-rest-apis-3ddp</link>
      <guid>https://dev.clauneck.workers.dev/janardan_joshi_6f21026e06/the-no-panic-guide-to-json-schema-validation-for-rest-apis-3ddp</guid>
      <description>&lt;p&gt;A few years ago, I shipped what looked like a textbook, harmless API update.&lt;/p&gt;

&lt;p&gt;The endpoint returned valid JSON. Every single local test passed with flying colors. We hit deploy, dusted off our hands, and went to grab coffee.&lt;/p&gt;

&lt;p&gt;Ten minutes later, Slack exploded. The frontend team was reporting errors across the entire app.&lt;/p&gt;

&lt;p&gt;The culprit? A seemingly minor refactor: I had changed a property named name to fullName, and dropped another field entirely because "surely nothing is using this anymore." (Narrator: Everything was using it.)&lt;/p&gt;

&lt;p&gt;The API wasn't throwing 500 errors. It was technically functioning perfectly—but every single client depending on that old data structure was completely broken. That’s exactly the kind of production nightmare JSON Schema is designed to kill.&lt;/p&gt;

&lt;p&gt;What is JSON Schema (and Why Should You Care)?&lt;/p&gt;

&lt;p&gt;At its core, JSON Schema is just a blueprint for your data. &lt;br&gt;
If the data doesn't fit the dress code, it doesn't get in.&lt;/p&gt;

&lt;p&gt;A quick look at a basic JSON-schema:&lt;br&gt;
{&lt;br&gt;
  "type": "object",&lt;br&gt;
  "required": ["id", "name", "email"],&lt;br&gt;
  "properties": {&lt;br&gt;
    "id": { "type": "integer" },&lt;br&gt;
    "name": { "type": "string" },&lt;br&gt;
    "email": { "type": "string", "format": "email" }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The Drift Dilemma&lt;/p&gt;

&lt;p&gt;Imagine your API spits out this clean response today:&lt;br&gt;
JSON&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "id": 1,&lt;br&gt;
  "name": "John Doe",&lt;br&gt;
  "email": "&lt;a href="mailto:john@example.com"&gt;john@example.com&lt;/a&gt;"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A month from now, a well-meaning developer refactors the endpoint, and it morphs into this:&lt;br&gt;
JSON&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "userId": 1,&lt;br&gt;
  "fullName": "John Doe"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;To a basic uptime monitor, the API looks healthy. It’s returning a 200 OK and a valid JSON object. But your frontend applications are screaming because id became userId, name became fullName, and email vanished into the void.&lt;/p&gt;

&lt;p&gt;Without automated schema validation, you usually find out about this contract drift after your users start complaining.&lt;br&gt;
The Basics: Types, Nesting, and Strictness&lt;/p&gt;

&lt;p&gt;JSON Schema is incredibly flexible and easily maps to standard data types (string, integer, number, boolean, object, array, and null).&lt;/p&gt;

&lt;p&gt;But it’s not just about playing hide-and-seek with missing keys. Where schema validation really saves your skin is handling data integrity when things get messy under the hood.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "Accidental String" Trap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Type mismatches are incredibly easy to slip into a codebase during a quick refactor. For example, if someone inadvertently wraps an age in quotes—sending "25" as a string instead of a clean integer like 25—the frontend code might try to perform math on it or break entirely. A strict schema validator flags that data type shift instantly before it can wreck anything downstream.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Taming Nested Chaos&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's be honest: real-world APIs are almost never neat and flat. They are complex webs of nested objects and arrays. JSON Schema lets you map out these deep relationships without breaking a sweat.&lt;/p&gt;

&lt;p&gt;Take a look at how it handles a list of tags tucked inside a user profile:&lt;br&gt;
JSON&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "type": "object",&lt;br&gt;
  "properties": {&lt;br&gt;
    "user": {&lt;br&gt;
      "type": "object",&lt;br&gt;
      "properties": {&lt;br&gt;
        "tags": {&lt;br&gt;
          "type": "array",&lt;br&gt;
          "items": { "type": "string" }&lt;br&gt;
        }&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;If a bug in your database query suddenly returns a mixed array like ["developer", 123], the validator immediate halts the response. It knows exactly what a valid array should look like and refuses to let a rogue integer slip by.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Locking the Back Door with additionalProperties
By default, JSON Schema allows extra fields it doesn't recognize. To stop unexpected data from quietly bleeding into your responses, you can pass a secret weapon:
JSON&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;{&lt;br&gt;
  "additionalProperties": false&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now, if someone tries to slip unauthorized fields into the payload, the validator shuts it down. The Production Reality Check: Dynamic Data&lt;/p&gt;

&lt;p&gt;If you try to implement raw schema validation in the real world, you will immediately run into the "Dynamic Data Problem." APIs love to return things like this:&lt;br&gt;
JSON&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "timestamp": "2026-06-26T10:21:15Z",&lt;br&gt;
  "request_id": "7dc2c5abc123",&lt;br&gt;
  "trace_id": "abf942xyz789"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;These values change on literally every single request. Technically the data is different, but functionally, your API isn't broken. If your validation pipeline treats every changing timestamp as a breaking change, you’ll end up with alert fatigue and a team that hates you.In practice, the trick is to validate the structure of these fields (e.g., ensuring timestamp is always a valid ISO date string) while ignoring the actual shifting value. Moving Beyond Manual Testing&lt;/p&gt;

&lt;p&gt;Manual inspection during code reviews is great, but humans are remarkably bad at noticing that a nested key changed from camelCase to snake_case at 4:30 PM on a Friday.Integrating schema validation directly into your CI/CD pipeline acts as a safety net:&lt;/p&gt;

&lt;p&gt;[Developer Commit] ➔ [Run Unit Tests] ➔ [Validate Against JSON Schema] ➔ [Safe Deployment]&lt;/p&gt;

&lt;p&gt;If a pull request accidentally breaks the data contract, the build fails before it ever touches a production server.What We Built to Solve This&lt;/p&gt;

&lt;p&gt;Wrestling with writing, maintaining, and ignoring dynamic fields across dozens of schemas is exactly why we built &lt;a href="https://fixzi.ai/" rel="noopener noreferrer"&gt;Fixzi.ai&lt;/a&gt;.Instead of forcing you to manually map out massive JSON structures or guess which fields are going to throw false positives in production, &lt;a href="https://fixzi.ai/" rel="noopener noreferrer"&gt;Fixzi&lt;/a&gt; automates the heavy lifting. It continuously monitors your live APIs, automatically maps your contracts, highlights actual breaking differences, and smartly ignores the chaotic dynamic fields (like tokens and timestamps) that you shouldn't be wasting time worrying about.It turns API validation from a tedious chore into background peace of mind.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
