<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[Drupal Blog]]></title>
        <description><![CDATA[RSS feed for all Drupal blog posts.]]></description>
        <link>https://blog.hrishikeshdalal.tech/drupal/drupal</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Sat, 24 Oct 2026 07:35:22 GMT</lastBuildDate>
        <atom:link href="https://blog.hrishikeshdalal.tech/drupal/drupal/rss.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Sat, 24 Oct 2026 07:35:20 GMT</pubDate>
        <language><![CDATA[en]]></language>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[AI Recipe Generator]]></title>
            <description><![CDATA[The AI Recipe Generator GSoC project, highlighting the development of AI-powered Drupal recipe creation, the Smart Editor, and tools for non-technical users to build websites faster.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a867eb00b4cdd3a65f15522</link>
            <guid isPermaLink="false">6a867eb00b4cdd3a65f15522</guid>
            <pubDate>Thu, 20 Aug 2026 04:12:32 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcome to our journey here!</strong></p>
<p>This blog has all the details what we have done here. Before we start out, let me provide you with all the master links which might be super useful for you:</p>
<p><a href="https://www.drupal.org/project/ai_recipe_generator">Module Site - Config Chef - AI Recipe Generator</a></p>
<p><a href="https://www.drupal.org/project/ai_recipe_generator/issues/3592990">Master Issue Tracker R1</a></p>
<p><a href="https://www.youtube.com/@hrishikesh-dalal-1">Youtube Channel</a></p>
<p><a href="https://drive.google.com/file/d/1a88jxF_nCHMvtJq--EB-yQQfP9S6vExd/view?usp=sharing">My Propoal</a></p>
<blockquote>
<p>If you need any help with Proposal or any more questions, feel free to reach out to me on <a href="https://www.linkedin.com/in/hrishikeshdalal/">LinkedIn</a>.</p>
</blockquote>
<p>With that done, let&#39;s get started.</p>

<h2>My Deliverables</h2>

<figure><iframe src="https://www.youtube.com/embed/uSzyFrsBN_c" title="YouTube video" allowfullscreen></iframe></figure>
<pre><code class="language-bash">composer require &#39;drupal/ai_recipe_generator:^1.0@beta&#39;</code></pre>
<h2>Project Abstract</h2>
<h3>What is Drupal?</h3>
<p>Drupal is a free, open-source Content Management System (CMS) and digital experience platform built on PHP and Symfony. While platforms like WordPress focus heavily on out-of-the-box ease of use, Drupal is engineered as an enterprise-grade framework for building complex, data-heavy, and high-traffic website</p>
<h3>About this project</h3>
<p>The project was built with just one thing in mind - to help non-technical people with their Drupal development. So it sits at the heart of Drupal development. We use Drupal Recipes in order to enable this faster. Drupal Recipes are like website blueprints that will help you to build websites. But the main drawback here was that they are really technical to write!</p>
<p>Inorder to help them, we have built this solution which uses AI as the core backbone and empowers users to rapid build and prototype this! Aimed at helping thousands of people.</p>
<p>The features that I have made:
1). <strong>AI Recipe Chat:</strong> Pick a model, generate recipes via natural prompts, and use the <strong>Explain</strong> wand for instant breakdowns.</p>
<p>2). <strong>Smart Editor:</strong> Tweak code manually, validate YAML/schema rules, or let AI refine your changes with <strong>Apply Edits</strong>.</p>
<p>3). <strong>Preview, Export &amp; Save:</strong> Inspect outputs live, download as <code>.zip</code>, or deploy directly to <code>web/recipes/custom</code>.</p>
<p>4). <strong>LLM Arena:</strong> Broadcast one prompt across multiple models to compare results side-by-side.</p>
<p>5). <strong>Site Context Aware:</strong> Pulls live Drupal architecture into prompts to eliminate duplicate fields and configs.</p>
<p>6). <strong>Master Prompting:</strong> Enforce system-level persona instructions globally across all AI requests.</p>
<p>7). <strong>Benchmarking Metrics:</strong> Track token latency, success rates, and model accuracy via visual dashboards.</p>

<h2>Major Accomplishments</h2>
<h3>AI Chat Interface</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913192/drupal/xxck8p5tyybsbvm1a2bo.png" alt="Drupal image" /></figure>
<p>The AI Chat Interface instantly translates plain English prompts into valid, YAML-compliant Drupal recipes while leveraging live site context to prevent duplicate fields. It features an intelligent editor with schema validation and AI-driven error correction, alongside an educational &quot;Explain&quot; wand that breaks down the generated configurations. Additionally, it enforces global system personas for consistent quality and streamlines deployment through live previews, direct server saves, and <code>.zip</code> exports.</p>

<h3>AI Arena</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913013/drupal/giqjlxkmqcaqbrj7ghed.png" alt="Drupal image" /></figure>
<p>The AI Arena facilitates simultaneous testing by broadcasting a single master prompt to multiple selected LLMs at once. This allows developers to evaluate outputs side-by-side and quickly determine which model generates the most accurate and compliant code.</p>

<h3>LLM Benchmarker</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783916694/drupal/gqivemly8oxvc2ggto79.png" alt="AI Bench Marker" /><figcaption>AI Bench Marker</figcaption></figure>
<p>The LLM Benchmarker features a visual analytics dashboard to track overall system and model performance. It effectively monitors key metrics, including total successful runs, average token latency, generation costs, and model accuracy.</p>

<h2>Technical Challenge Overcame</h2>
<h3>Chat Interface Layout</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784954543/drupal/sb235m8vqkdiwuuxusxj.png" alt="Drupal image" /></figure>
<p><strong>Problem:</strong> Used a twig template, which caused rendering issues (data loading and UI) as it was difficult to manage.</p>
<p><strong>Solution</strong>: Simplified the twig template, did the heavy lifting in the controller.</p>
<p><strong>Impact:</strong> A smoother loading and transition, which was then scalable when had a lot of data in the database. </p>

<h3>Markdown Paradox</h3>

<figure><img src="https://miro.medium.com/v2/resize:fit:1100/format:webp/1*-i-vTy8MTnoxfRgsLMuTag.png" alt="Drupal image" /></figure>
<blockquote>
<p>If you are really curious I have a <a href="https://medium.com/@1010hrishikesh/markdown-paradox-c5da304d3735">detailed blog</a> with proper testing</p>
</blockquote>
<p><strong>Problem:</strong> The YAML directly returned by AI caused formatting issues when directly used. </p>
<p><strong>Solution</strong>: Used a Drupal Module for YAML editing and built a custom parser.</p>
<p><strong>Impact:</strong> The formatting and display of YAML became seamless. </p>

<h3>Queries Multiple AI Models</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913047/drupal/ue09eoxsdbnjjigcw2vz.png" alt="Drupal image" /></figure>
<p><strong>Problem:</strong> Queries multiple AI Modules was resulting in higher processing time and code breakage. </p>
<p><strong>Solution</strong>: Implemented the Drupal Batch API to query multiple models at once.</p>
<p><strong>Impact:</strong> Users could now query multiple models parallelly to get which model performs best for their usecase.</p>

<h2>Timeline Chart</h2>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1787213661/drupal/byklsq282wxinof4eajg.png" alt="My estimated Timeline" /><figcaption>My estimated Timeline</figcaption></figure>
<p>I was my planned timeline of what I wanted to it. But in reality I completed things was sooner than the proposed timeline which gave me &amp; my mentor space to work on further integration and adoption with newer versions of Drupal : )</p>

<h2>Code Quality &amp; Testing</h2>
<p>This is the most important aspect while coding. Drupal has in depth version of testing and maintaining the code quality. I made sure that after each commit the pipeline was green so the code quality was upto standard.</p>
<p>1). <strong>Build Stage:</strong>
 <strong>Composer Dependency Resolution:</strong> Installs and verifies project packages across baseline, maximum PHP versions, next minor Drupal releases, and previous major versions.</p>
<p>2). <strong>Validate Stage:</strong></p>
<ul>
<li><strong>Syntax &amp; Asset Linting:</strong> Checks <code>composer.json</code> syntax (<code>composer-lint</code>), scans for typos (<code>cspell</code>), lints JavaScript (<code>eslint</code>), and validates stylesheets (<code>stylelint</code>).</li>
<li><strong>Code Standards &amp; Static Analysis:</strong> Enforces Drupal PHP coding standards (<code>phpcs</code>) and detects bugs, type mismatches, and deprecations across environments (<code>phpstan</code>).</li>
</ul>
<p>3). <strong>Test Stage:</strong></p>
<ul>
<li><strong>Automated Test Execution:</strong> Runs PHPUnit suites across baseline and compatibility matrices (maximum PHP version, next minor, and previous major Drupal releases) to ensure all tests pass.</li>
</ul>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1787214228/drupal/axnvqtxl02juxnxchjzm.png" alt="Pipeline" /><figcaption>Pipeline</figcaption></figure>
<h2>Future Development</h2>
<p>The scope of this project is complete. A further focus could be on working on the security patch. The work is in progress.</p>

<h2>Acknowledgements</h2>
<p>I would like to thank my mentor Rajas Samse along with the entire Drupal community as the whole for providing me with such a great opportunity to work and develop a useful application.</p>
<p>Kudos to them!</p>

<h2>Useful Links / Sources</h2>
<p>Please feel free to check my website or my other blog about Drupal Hope they provide you with some insight. In case of any questions feel free to reach out to me.</p>
<p>Thanks a lot!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1787199133/drupal/zww3fvohjuza3qryb0xd.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal: Week 12 - The last stop]]></title>
            <description><![CDATA[The final week of my Google Summer of Code journey with Drupal, focusing on achieving Drupal 12 compatibility for the AI Recipe Generator project.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a82aecb8adae8fa7d64124a</link>
            <guid isPermaLink="false">6a82aecb8adae8fa7d64124a</guid>
            <pubDate>Mon, 17 Aug 2026 06:48:43 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>WELCOMMEEEE to WEEK 12 of Drupal x GSoC!!!</strong></p>
<p>This is the last week of our Google Summer of Code journey. Has an amazing journey with Drupal - one that I will never forget.</p>
<p>So for this last week, Drupal was giving me a gift, D-12 Compatibility. The Drupal Bot gave me a PR: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3617107">Drupal Bot PR</a>.</p>
<p>Quickly reviewed this PR and that was all for week 12. <strong>Compatibility with D-12.</strong></p>
<p>Will post a wrap up blog!</p>
<blockquote>
<p>Until then, keep exploring, keep building!</p>
</blockquote>

<figure><iframe src="https://www.youtube.com/embed/uSzyFrsBN_c" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786948036/drupal/wfbsihx7cjlpcq6k8laj.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal: Week 11 - The Small Changes]]></title>
            <description><![CDATA[Week 11 of my GSoC journey with Drupal focuses on refining administrative routes, cleaning up the UI, and standardizing menu links to improve the module's overall usability and structure.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a81d657d90556d982ff2d27</link>
            <guid isPermaLink="false">6a81d657d90556d982ff2d27</guid>
            <pubDate>Sun, 16 Aug 2026 15:25:12 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomeee to Week 11 of Drupal x GSoC - the penultimate time</strong></p>
<p>After the excitement of pushing our first Beta release last week, we took a slight breather this week to focus on some essential housekeeping. When you are building a complex module quickly, little inconsistencies can start to creep in. This week, we focused entirely on solving one specific, but very important—issue: cleaning up our administrative routes and refining the UI.</p>
<h2>The Problem: Scattered Routes and Hardcoded Links</h2>
<p>During the initial development phases, our administrative paths became a bit fragmented. For example, some pages were sitting at <code>/admin/config/ai-recipe</code>, while others were at <code>/admin/config/development/ai-recipes</code>.</p>
<p>This inconsistency led to a few annoying problems:</p>
<p>1). <strong>Orphaned Settings:</strong> Our settings page wasn&#39;t properly nested in the main Drupal configuration menu, making it hard to find.
2). <strong>Brittle Code:</strong> The sidebar links in our chat dashboard (<code>RecipeChatForm.php</code>) were using hardcoded <code>&lt;a href=&quot;#&quot;&gt;</code> tags. If a path ever changed, those links would break immediately.
3). <strong>Cluttered UI:</strong> The settings form had a bit too much heavy text, making it feel clunky rather than actionable.
4). <strong>YAML Quirks:</strong> In our routing file, we were using the literal string <code>&#39;NULL&#39;</code> instead of standard YAML nulls, which isn&#39;t best practice.</p>
<h2>The Solution: Tying It All Together</h2>
<p>To make the module feel native and polished, we completely overhauled how these paths and links are structured. Here is exactly what we did:</p>
<h3>1. Unifying the Routing Namespace</h3>
<p>We standardized all of the module&#39;s routes in <code>ai_recipe_generator.routing.yml</code> so they sit under one predictable namespace: <code>/admin/config/development/ai-recipe-generator/...</code>. Now, everything is logically grouped together.</p>
<h3>2. Wiring Up the Menu Links</h3>
<p>We created an <code>ai_recipe_generator.links.menu.yml</code> file. This tells Drupal exactly where to put our pages in the admin menu. Now, you can easily find the Settings, Dashboard, Prompt Arena, and Recipe Chat neatly nested right under <strong>Configuration &gt; Development</strong>.</p>

<figure><iframe src="https://www.youtube.com/embed/uSzyFrsBN_c" title="YouTube video" allowfullscreen></iframe></figure>
<pre><code class="language-yaml"># Main Settings Page 
ai_recipe_generator.chat_dashboard:
  title: &#39;Config Chef - AI Recipe Generator&#39;
  description: &#39;Configure settings and models for the AI Recipe Generator.&#39;
  parent: system.admin_config_development
  route_name: ai_recipe_generator.chat_dashboard
  weight: 100

# Sub-menu items
ai_recipe_generator.dashboard:
  title: &#39;Dashboard&#39;
  parent: ai_recipe_generator.chat_dashboard
  route_name: ai_recipe_generator.dashboard
  weight: 10

ai_recipe_generator.arena:
  title: &#39;Prompt Arena&#39;
  parent: ai_recipe_generator.chat_dashboard
  route_name: ai_recipe_generator.arena
  weight: 20

ai_recipe_generator.settings:
  title: &#39;Settings&#39;
  parent: ai_recipe_generator.chat_dashboard
  route_name: ai_recipe_generator.settings
  weight: 30
</code></pre>
<h3>3. Dynamic Link Generation</h3>
<p>We went into <code>RecipeChatForm.php</code> and stripped out all the hardcoded string paths. We replaced them with Drupal’s dynamic <code>Url::fromRoute()</code> class. This means Drupal will now automatically generate the correct URL based on the route name, making the code much more resilient. If we ever change a URL path in the future, the sidebar links will update automatically!</p>
<h3>4. Refining the UI and YAML Syntax</h3>
<p>We replaced the string <code>&#39;NULL&#39;</code> with the standard YAML null character (<code>~</code>) for our optional <code>{thread_id}</code> parameter in the routing file. We also cleaned up the settings page by moving bulky documentation out of the way and utilizing the <code>#description</code> property on individual form fields. This keeps the UI focused and gives users targeted help exactly where they need it.</p>
<h2>What&#39;s Left?</h2>
<p>To wrap this issue up, the final remaining tasks were pretty simple:</p>
<p>1). Doing a quick sweep of the codebase (including Twig templates) to ensure no old legacy paths were hiding in the cracks.
2). Running <code>drush cr</code> (Cache Rebuild) to register the new routing and menu link trees.
3). Clicking through the sidebar to verify all the new dynamic routes point exactly where they should.</p>
<p>It might seem like a small task compared to building AI features, but these architectural cleanups are what separate a good module from a great one.</p>

<p><strong>Catch you up next week!</strong></p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786893104/drupal/bt3d5fi21dqnpysjz4ol.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x GSoC : Week 10 - The first release]]></title>
            <description><![CDATA[Week 10 of my GSoC journey marks a major milestone: the first official Beta release of my project. I walk through the process of preparing a Drupal module for release, including quality checks, testing, and documentation.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a81cf28b1aada9e712cc75e</link>
            <guid isPermaLink="false">6a81cf28b1aada9e712cc75e</guid>
            <pubDate>Sun, 16 Aug 2026 14:54:32 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomeee to Week 10 of Drupal x GSoC</strong></p>
<p>This week was special, this was close to my heart. Finally did my FIRST RELEASE!!! </p>
<p>Ever since I have seen people releasing their beta, I knew I wanted to do the same. So after finishing the testing and the changes, this was BETA 1 Launch week. Super interesting. Lets dig in!</p>
<p>Alternate: you could follow along this official documentation and skip this <a href="https://www.drupal.org/docs/develop/git/git-for-drupal-project-maintainers/creating-a-project-release">Creating a Project Release</a></p>

<figure><iframe src="https://www.youtube.com/embed/N4V_2_nAgEE" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786891678/drupal/bxl3djavyuwxha1xfv2f.png" alt="Drupal image" /></figure>
<p>Moving from active development (<code>dev</code>) to a <strong>Beta</strong> release means our primary feature set is code-complete and ready for broader community testing.</p>
<p>If you have ever wondered how a release actually comes together in the Drupal ecosystem, here is a simple breakdown of the steps involved.</p>
<h2>What Goes Into Creating a Drupal Release?</h2>
<p>Creating a release on Drupal.org is straightforward, but it requires careful preparation so users get stable code and clear documentation.</p>
<h3>1. Final Quality Checks &amp; Green Pipeline</h3>
<p>Before touching any release buttons, we made sure the codebase was completely clean:</p>
<p>1). All unit, kernel, and functional JavaScript tests passed without issues.
2). Coding standards (<code>phpcs</code>) and static analysis (<code>phpstan</code>) passed with zero errors.
3). Module dependencies and Drupal core compatibility (e.g., <code>core_version_requirement: ^10.3 || ^11</code>) were verified in the <code>.info.yml</code> file.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786891750/drupal/swtgsudnbo6wmbcz7gxd.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786891815/drupal/jlbivjqz5hjdvsfocdz9.png" alt="Drupal image" /></figure>
<h3>2. Preparing the Changelog and Release Notes</h3>
<p>A good release note tells users exactly what has changed and what to test. We rounded up all the major features and fixes implemented so far. </p>
<p><em>This is generally when you have multiple major developments. You could technically do this for a beta release as well but since our scope is just limited to me, I have skipped this.</em></p>
<p>The major steps are:</p>
<ul>
<li>The interactive Recipe Chat dashboard.</li>
<li>AI orchestration and context extraction services.</li>
<li>YAML parsing and schema validation utilities.</li>
<li>The complete test suite and CI pipeline integration.</li>
</ul>
<h3>3. Tagging the Release in Git</h3>
<p>Drupal releases are tied directly to Git tags following <strong>Semantic Versioning</strong>. Super useful sources: <a href="https://www.drupal.org/docs/develop/git/git-for-drupal-project-maintainers/types-of-releases-for-full-projects">Types of Releases</a> &amp; <a href="https://www.drupal.org/docs/develop/git/git-for-drupal-project-maintainers/release-naming-conventions">Release Naming Convention</a>.</p>
<p><strong>What is a tag?</strong></p>
<p>A tag is like a permanent bookmark or snapshot for a specific moment in a project&#39;s history. While branches are meant to keep moving forward as new code is added, a tag stays locked exactly on the commit where it was created. Developers primarily use tags to mark official release versions, like v1.0 or v2.5, so anyone can always go back and download that exact, specific version of the software without worrying about future code changes altering or breaking it.</p>
<p>Once the <code>main</code> branch was verified, creating the tag took just two commands:</p>

<pre><code class="language-bash"># Create an annotated git tag
git tag -a 1.0.0-beta1 -m &quot;Release 1.0.0-beta1: AI Recipe Generator&quot;

# Push the tag up to the Drupal.org repository
git push origin 1.0.0-beta1</code></pre>
<h3>4. Creating the Release Node on Drupal.org</h3>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786892473/drupal/vpjc3a4gqo1ki4v8nxct.png" alt="In this section, click on add new release" /><figcaption>In this section, click on add new release</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786892506/drupal/e6u8srzxoemivvdxgskq.png" alt="Here select the appropriate release and you are done!" /><figcaption>Here select the appropriate release and you are done!</figcaption></figure>
<p>Once Drupal.org detects the new tag in the repository, the final step happens on the web interface:</p>
<p>1). Navigate to the project page and click <strong>Add new release</strong>.
2). Select the <code>1.0.0-beta1</code> tag from the dropdown.
3). Paste in the release notes (highlighting key features, how to install, and known caveats).
4). Hit <strong>Save</strong> to publish.</p>
<p>Drupal.org automatically builds the release package and generates the Composer-compatible metadata so developers can easily install it.</p>
<h2>How to Try the Beta</h2>
<p>Anyone can now pull down and test the beta version using Composer (You must have seen the glimpse picture at the start):</p>

<pre><code class="language-bash">composer require &#39;drupal/ai_recipe_generator:^1.0@beta&#39;</code></pre>
<p>Thanks you! Catch ya later : )</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786891580/drupal/qfdsul2kxiec09dwep3q.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal: Week 9 - Going into the last sprint]]></title>
            <description><![CDATA[Week 9 of GSoC x Drupal focuses on stability, featuring the implementation of a comprehensive test suite covering unit, kernel, and functional tests, alongside CI/CD pipeline improvements for the AI Recipe Generator.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a81c76b4de6d997c898af69</link>
            <guid isPermaLink="false">6a81c76b4de6d997c898af69</guid>
            <pubDate>Sun, 16 Aug 2026 14:21:31 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomme to Week 9 of GSoC x Drupal</strong></p>
<p>Up until now, a lot of our focus has been on building features, tweaking UI, and getting the AI integration just right. But this week, we took a step back to focus on something arguably even more important: <strong>stability</strong>.</p>
<p>We tackled two massive milestones this week: writing a comprehensive test suite for the AI Recipe Generator and finally fixing up our CI/CD pipeline so everything runs automatically. Let’s dive into what we got done!</p>

<figure><iframe src="https://www.youtube.com/embed/nXZ5a41bBdo" title="YouTube video" allowfullscreen></iframe></figure>
<h2>1. Building the Test Suite</h2>
<p>You can&#39;t have a reliable module without tests. From the previous blogs, I have covered quite a few about testing and what all we have been doing. This is the overview of our compelte testing, you can view the entire testing in the testing folder : )</p>
<p> We’ve set up a full testing directory that covers everything from quick syntax checks to headless browser UI testing.</p>
<ul>
<li><strong>Unit Tests (<code>tests/src/Unit/</code>):</strong> These are our fastest tests. They run without bootstrapping Drupal and use mocked dependencies. They validate things like <code>YamlServicesTest</code> (checking raw YAML syntax), <code>ContextExtractorTest</code> (gathering site data for the AI), and <code>RecipeGenerationServiceTest</code>.</li>
<li><strong>Kernel Tests (<code>tests/src/Kernel/</code>):</strong> This is where we test integration with a partial Drupal bootstrap and an active database. Tests like <code>RecipeStorageServiceTest</code> ensure our database CRUD operations work perfectly, while <code>ArenaServiceTest</code> validates our evaluation logic.</li>
<li><strong>Functional Tests (<code>tests/src/Functional/</code>):</strong> Full PHP-driven browser tests with a complete Drupal installation to test backend form submission logic.</li>
<li><strong>Functional Javascript Tests (<code>tests/src/FunctionalJavascript/</code>):</strong> The real deal. These use Chrome/Selenium to test actual UI interactions, like AJAX requests and dynamic dropdowns in the <code>RecipeChatFormTest</code>.</li>
</ul>
<h3>Running the Tests with DDEV</h3>
<p>To make life easy for any developer jumping into this project, everything is configured to run smoothly inside DDEV.</p>
<p>Before running the browser tests, you just need to make sure your <code>.ddev/config.yaml</code> has the required environment variables under <code>web_environment</code> so PHPUnit can reach the site and the Selenium Chrome driver:</p>

<pre><code class="language-yaml">web_environment:
  - SIMPLETEST_BASE_URL=http://localhost
  - SIMPLETEST_DB=mysql://db:db@db:3306/db</code></pre>
<p>Once that is set (and you&#39;ve run <code>ddev restart</code>), you can run the whole suite or break it down by type:</p>

<pre><code class="language-bash"># Run all tests
ddev exec phpunit --configuration=web/core web/modules/custom/ai_recipe_generator/tests

# Run just the lightning-fast Unit tests
ddev exec phpunit --configuration=web/core web/modules/custom/ai_recipe_generator/tests/src/Unit

# Run full browser Javascript tests
ddev exec phpunit --configuration=web/core web/modules/custom/ai_recipe_generator/tests/src/FunctionalJavascript
</code></pre>
<p><em>Tip: If you want to see test names as they run, just append <code>--testdox</code> to your command!</em></p>
<h3>Testing Conventions We Follow</h3>
<p>To keep the codebase clean moving forward, we’ve established a few ground rules for new tests:</p>
<ol>
<li><strong>Intent-first naming:</strong> Method names should clearly state what they are testing.</li>
<li><strong>Process isolation:</strong> Adding <code>#[RunTestsInSeparateProcesses]</code> to Kernel and Functional tests (crucial for Drupal 11.3+ and 12).</li>
<li><strong>Bypass HTML5 validation:</strong> When testing AJAX in FunctionalJS, we inject <code>novalidate</code> attributes to ensure the request actually reaches the server.</li>
<li><strong>Arrange/Act/Assert:</strong> Non-trivial tests use these comment blocks to keep the logic easy to read.</li>
</ol>
<h2>2. Fixing the Pipeline</h2>
<p>If you have been working on some PR or somehow on Drupal code, you know seeing the pipeline green is the most important aspect of it. Quite a few times it drains your soul - why did I get stuck here?</p>
<p>After getting all the test cases written and passing locally, the next big hurdle was the CI/CD pipeline. Previously, the pipeline was throwing some frustrating errors and failing to properly spin up the environments needed to run our heavy FunctionalJS tests.</p>
<p>I spent the second half of the week debugging the configuration, ensuring the database services and Chrome headless drivers were properly communicating inside the runner. <strong>The pipeline is now fixed and fully green!</strong> Every time we push a commit, it automatically runs our linting checks (<code>phpcs</code>, <code>phpstan</code>) and executes the entire PHPUnit suite.</p>
<p>I will cover testing how to make pipeline green in another blog.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786890457/drupal/tbg5zildd4dmk40ef2cf.png" alt="This is beyond happiness" /><figcaption>This is beyond happiness</figcaption></figure>
<p>See ya next week!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1786890610/drupal/x7x5jg0pgbbrrbtr54rb.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal: Week 8 - Diving Deeper into Drupal]]></title>
            <description><![CDATA[A recap of Week 8 in Google Summer of Code with Drupal, focusing on fixing UI issues and diving deep into writing Kernel Tests for application testing.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a644183ad74e499e1527bd2</link>
            <guid isPermaLink="false">6a644183ad74e499e1527bd2</guid>
            <pubDate>Sat, 25 Jul 2026 04:54:27 GMT</pubDate>
            <content:encoded><![CDATA[<h2>Welcome to Week 8 of GSOC x Drupal!!!!</h2>
<p>This week was more about fixing the issues that persisted. Some of them were UI and adding test cases. We dived deeper into testing.</p>
<p>Without further adieu, let me give you a glimpse first. </p>

<figure><iframe src="https://www.youtube.com/embed/yDEoP691Bgc" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784954543/drupal/sb235m8vqkdiwuuxusxj.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784954453/drupal/twnkuolnnov1gjbbuj5i.png" alt="Drupal image" /></figure>
<h2>Testing our application</h2>
<p>Testing is one of the backbone of any application. I also started out the testing journey. Drupal testcases are super useful, you can break them down into different files with specific purpose. Let me again take you through this different tests</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784955005/drupal/mbqxzlhxkni247t7h61s.png" alt="Drupal image" /></figure>
<p>This time we created a bunch of Kernel tests. </p>
<h3>What are Kernel Tests?</h3>
<p>Kernel tests (using the KernelTestBase class) are PHPUnit-based integration tests. They boot a minimal, lightweight version of the Drupal environment (the &quot;kernel&quot;) rather than a fully installed site.</p>
<h3>What are they used for</h3>
<p>They are used to test how your code interacts with core Drupal subsystems: like the database, Entity API, services, and configuration: without the massive performance penalty of loading the full HTML presentation layer or simulating a web browser.</p>
<blockquote>
<p>The goal for writing any test case is to cover all the possible combinations, your application may encounter. </p>
</blockquote>
<h3>Diving Deeper into Kernel Test cases</h3>
<p><strong>Explicit Setup Requirements</strong>
Unlike Functional tests, which install a full copy of Drupal behind the scenes, Kernel tests start completely bare. If your test needs a module,<em>you must explicitly declare it in the $modules array.</em> Furthermore, just enabling a module doesn&#39;t install its database tables or default configuration; you must manually trigger these in your setUp() method using calls like $this-&gt;installEntitySchema(&#39;node&#39;) or $this-&gt;installConfig([&#39;my_module&#39;]).</p>
<p>Configuring this is slightly more complex than Unit Test cases, as it will take more time.</p>
<p><strong>Access to the Database and Container</strong>
Kernel tests connect to a real, isolated test database and fully compile the Drupal service container. This is their primary advantage over Unit tests:<strong>you can write real data to tables and retrieve real services</strong> (like the database connection or entity type manager) instead of writing dozens of complex, fragile mocks.</p>
<p><strong>The Missing Browser (and Recent Upgrades)</strong>
Historically, Kernel tests were strictly for backend APIs. There is no logged-in user, no active theme, and no browser.
However, recent additions to Drupal core (via HttpKernelUiHelperTrait) now allow Kernel tests to make HTTP requests using $this-&gt;drupalGet(). Instead of spinning up a browser, these tests pass requests directly to Drupal&#39;s HTTP kernel. This allows you to test page routing and text responses much faster than before, though you still cannot submit forms or run Javascript.</p>
<p><strong>When NOT to Use Them</strong>
You should avoid Kernel tests if:</p>
<p>i. A pure Unit test will work (i.e., you can easily mock all dependencies).
ii. You need to test form submissions or complex UI interactions.
iii. The functionality behaves differently depending on the active front-end theme.</p>

<h3>Demystifying Drupal Recipes</h3>
<p>Alongside testing, a major part of the work this week involved wrangling Drupal Recipes. If you haven&#39;t worked with them yet, they are fundamentally different from traditional modules. </p>
<p><strong>1. The &quot;One-Way Street&quot; Concept</strong>
Unlike modules, recipes are <em>applied</em>, not installed. Once you run a recipe, it dumps its configuration directly into your active site and steps away. </p>
<ul>
<li><strong>No Uninstall:</strong> Drupal doesn&#39;t track what a recipe did, so there&#39;s no native &quot;undo&quot; button.</li>
<li><strong>Idempotency:</strong> You can safely run the same recipe multiple times. It simply skips configuration that already exists.</li>
<li><strong>Direct Modification:</strong> Because recipes alter active config instantly, any YAML errors (like targeting non-existent config) will break the application.</li>
</ul>
<p><strong>2. The DDEV Pathing Fix</strong>
When applying recipes locally inside a DDEV environment, <code>drush</code> silently shifts its working directory to the <code>web</code> root. If your recipe is sitting in the project root, relative paths will fail or trigger confusing schema errors. </p>
<ul>
<li><em>The Fix:</em> Always execute using the absolute container path:</li>
</ul>

<pre><code class="language-bash">ddev drush recipe /var/www/html/recipes/your_recipe_name</code></pre>
<p><strong>3. Recovery Strategies (When things go wrong)</strong>
Because recipes lack an uninstall feature, you have to handle recovery externally if a recipe applies unwanted changes:</p>
<ul>
<li><strong>Database Snapshots (Best):</strong> To revert to a clean state.</li>
<li><strong>Config Rollback:</strong> overwrite the database with your clean YAML files (though you still have to manually uninstall any modules the recipe added).</li>
<li><strong>Manual Reversal:</strong> Read the <code>recipe.yml</code> and manually undo the changes in the Drupal UI.</li>
</ul>

<pre><code class="language-bash">ddev restore-snapshot [name] // database snaphots

ddev drush cim - y // config rollback</code></pre>
<p><strong>4. How to Actually Test Them</strong>
To avoid breaking your site while developing, use these workflows:</p>
<ul>
<li><strong>The Blank Canvas:</strong> Apply the recipe to a completely fresh Drupal install (like the Minimal profile) to ensure it isn&#39;t secretly relying on your specific local database configuration.</li>
<li><strong>Snapshot &amp; Diff:</strong> Take a snapshot, export your config (<code>drush cex</code>), run the recipe, export again, and use <code>git diff</code> to see the exact YAML changes.</li>
<li><strong>Automated PHPUnit:</strong> Use Drupal Core&#39;s <code>RecipeTestTrait</code> to write functional tests that spin up headless instances, apply the recipe, and programmatically verify the results.</li>
</ul>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784958687/drupal/wb6vh2whc8uvk3rwinfi.png" alt="Drupal image" /></figure>
<h3>Polishing the UI</h3>
<p>Beyond testing, a huge chunk of this week was dedicated to refining the AI Recipe Generator&#39;s chat dashboard. </p>
<p><strong>Bringing Markdown to Life (The Big Win)</strong>
When the AI explains a recipe, it returns the response formatted in Markdown. Initially, Drupal&#39;s standard string sanitation (<code>htmlspecialchars</code>) meant the browser was just spitting out literal characters like <code>### Requirements</code> and <code>**Node:**</code> instead of actual formatting. </p>
<p>To fix this, I integrated the <code>league/commonmark</code> library into the module&#39;s backend. I refactored the message rendering loop in <code>RecipeChatForm.php</code> so that before a message hits the template, it passes through the CommonMark engine. Now, the AI&#39;s raw text renders beautifully into structured HTML: clean headings, bold typography, and proper lists—while remaining secure against XSS vulnerabilities.</p>
<p><strong>The &quot;Save Button&quot; Dilemma</strong>
I also had to make a tough architectural call regarding file saving. Originally, we had a &quot;Save to Folder&quot; button that dumped the generated YAML directly into the <code>web/recipes</code> directory. This created two massive problems:</p>
<p>i.  <strong>Permissions:</strong> Standard web servers (<code>www-data</code>) don&#39;t have write access outside the web root, which would force admins to manually <code>chown</code>/<code>chmod</code> directories just to use the module. 
ii.  <strong>The Air Gap:</strong> Because recipes are a &quot;one-way street&quot; with no uninstall feature, automatically saving potentially hallucinated AI configurations directly to the server is incredibly dangerous.</p>
<p><em>The Solution:</em> I scrapped the server-save button entirely. Instead, we now rely on direct memory downloads using Symfony&#39;s <code>ResponseHeaderBag</code>. The generated YAML is served directly to the user&#39;s browser, forcing them to manually review it and intentionally apply it via the terminal.</p>
<p><strong>Squashing CSS and Layout Bugs</strong>
To finish out the week, I went on a UI bug-squashing spree:</p>
<ul>
<li><strong>Dynamic Validation Badges:</strong> The stats panel (Latency, YAML validity, Schema validity) used to be hardcoded green. I wired up the PHP backend to dynamically assign <code>.valid-badge</code>, <code>.invalid-badge</code>, or <code>.neutral-badge</code> CSS classes based on the actual live validation state.</li>
<li><strong>Button Makeovers:</strong> The &quot;Export&quot; buttons were rendering as ugly default gray browser inputs. I wrapped them in hidden submit layers, allowing us to style them as sleek, blue pill-shaped buttons with FontAwesome icons that match the design system.</li>
<li><strong>Flexbox Wrangling:</strong> The dashboard was plagued by double vertical scrollbars and horizontal overflow. By fixing <code>100vw</code> constraints, using <code>calc()</code> for margins, and applying <code>flex-grow: 1</code> to the Ace Editor, the layout now fits the viewport perfectly and the YAML editor finally stretches to fill the vertical space.</li>
<li><strong>CSS Variable Scope:</strong> Fixed an issue where user chat bubbles were rendering completely white. The global <code>.chat-dashboard-master</code> wrapper was missing from the main form array, preventing our custom CSS color variables from cascading down the DOM to the child elements.</li>
</ul>

<blockquote>
<p>Thanks for reading &amp; catch you up next time</p>
</blockquote>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1784960704/drupal/t5z4ccucjqwyifuqkuan.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal : Week 7 - Fixing the Arena]]></title>
            <description><![CDATA[Week 7 of GSoC x Drupal focuses on refining the LLM Arena interface, featuring improved chat layouts, seamless history navigation, and a more intuitive user experience.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a5458595ad52bccad42582f</link>
            <guid isPermaLink="false">6a5458595ad52bccad42582f</guid>
            <pubDate>Mon, 13 Jul 2026 03:15:37 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomme to Week 7 of GSoC x Drupal!!!</strong></p>
<p>This week we have made quite a bit of progress. Small victories but essential victories. I have worked on the LLM Arena Page, refining it better. Let&#39;s dive into it :)</p>
<p>Official Tracker: <a href="https://www.drupal.org/node/3608782">Week 7 Tracker</a></p>
<p>Glimpses on our work</p>

<figure><iframe src="https://www.youtube.com/embed/PBWIREWD3ec" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913013/drupal/giqjlxkmqcaqbrj7ghed.png" alt="Better Icons &amp; Layout" /><figcaption>Better Icons &amp; Layout</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913047/drupal/ue09eoxsdbnjjigcw2vz.png" alt="Same YAML Preview editor for all pages" /><figcaption>Same YAML Preview editor for all pages</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783913192/drupal/xxck8p5tyybsbvm1a2bo.png" alt="Better user interface with history sync" /><figcaption>Better user interface with history sync</figcaption></figure>
<h1>How I fixed the UI Interface?</h1>
<p>In Week 5 while fixing our blog we were stuck, had to rework a lot but now if we see this, we have successfully changed the UI.</p>
<blockquote>
<p>My goal was simple: transform the interface from a standard form-based page into something that the user can instantly use doesn&#39;t have to think - How do I use this?</p>
</blockquote>
<h2>The NEW User Experience</h2>
<p>Started to change UI, some changes</p>
<p><strong>Modern Chat Bubbles:</strong> Messages are now visually separated into intuitive left/right aligned chat bubbles. User prompts appear in a distinct dark blue, while system responses show up in a clean light gray.</p>
<p><strong>Seamless History Navigation:</strong> Your chat history now lives in a dedicated, scrollable sidebar. Clicking on a previous thread instantly loads that conversation into the main window without refreshing the page. I also added a <strong>&quot;Load More&quot;</strong> link to fetch older threads 10 at a time, keeping the initial page load blazing fast.</p>
<p><strong>Instant Context Resets:</strong> The <strong>&quot;+ New chat&quot;</strong> button has been fixed and upgraded. Clicking it immediately clears the canvas via AJAX so you can start brainstorming a new recipe right away.</p>
<p><strong>Actionable System Buttons:</strong> Underneath system responses, you will now see sleek, minimalist icon buttons for actions like <strong>&quot;Explain&quot;</strong> and <strong>&quot;Retry.&quot;</strong></p>
<p><strong>Graceful Error Handling:</strong> Previously, forgetting to select an LLM Provider and Model before hitting submit could cause a jarring page error. Now, you’ll get a smooth, animated red inline toast notification directly above the chat box reminding you to select your model.</p>
<p><strong>Polished UI Elements:</strong> I stripped out default generic buttons and basic emojis in the sidebar footer, replacing them with standard, cleanly spaced Font Awesome icons.</p>
<h2>Under the Hood: Technical Implementation</h2>
<p>Migrating a traditional web form into a dynamic, single-page application (SPA) style interface required several structural changes to the architecture, particularly within Drupal&#39;s Form API and AJAX framework.</p>
<h3>1. Refactoring the Sidebar for Precise AJAX Targeting</h3>
<p>The biggest hurdle was the chat history sidebar. Previously, it was hardcoded into a static Twig template (<code>ai-recipe-dashboard.html.twig</code>), meaning any interaction required a full page reload.</p>
<p>To fix this, I migrated the sidebar structure out of the Twig template and directly into the Form API inside <code>RecipeChatForm.php</code>. By doing this, I gained the ability to use precise AJAX targeting. I implemented dedicated callbacks:</p>
<ul>
<li><code>::ajaxRefreshSidebar</code>: Triggered by the &quot;Load More&quot; button to rebuild only the sidebar.</li>
<li><code>::ajaxRefreshMain</code>: Triggered by clicking a historical thread or &quot;+ New chat&quot; to refresh the main chat area independently.</li>
</ul>
<h3>2. Optimizing Database Queries with Pagination</h3>
<p>To prevent loading massive chat histories all at once, I updated the underlying data service. The signature for fetching threads was updated to include pagination parameters:
<code>RecipeStorageService::getRecentThreads(int $limit = 10, int $offset = 0): array</code></p>
<p>This ensures the database is only queried for 10 records at a time, dramatically improving memory usage and load times.</p>
<h3>3. Fixing Form Actions and Library Dependencies</h3>
<p>I squashed a few stubborn bugs related to form submission and rendering:</p>
<ul>
<li><strong>The 500 AJAX Error:</strong> The &quot;+ New chat&quot; button was missing a dedicated submit handler. I introduced <code>submitNewChat</code> to clear the <code>thread_id</code> and reset state variables (<code>latest_yaml</code>, <code>last_prompt</code>) safely.</li>
<li><strong>Invisible Action Buttons:</strong> The &quot;Explain&quot; and &quot;Retry&quot; buttons were originally configured with <code>#type =&gt; &#39;html_tag&#39;</code>, which breaks Drupal AJAX. I converted them to standard <code>#type =&gt; &#39;submit&#39;</code> elements, wrapping them in <code>#prefix</code> and <code>#suffix</code> to render the Font Awesome icons with a transparent, clickable CSS overlay. I also ensured <code>fontawesome</code> is properly attached via <code>RecipeChatController.php</code>.</li>
</ul>
<h3>4. Inline Validation over Fatal Errors</h3>
<p>I replaced a harsh <code>PluginNotFoundException</code> with a much friendlier user experience. If a user submits a prompt without selecting an LLM provider or model, I now intercept this in the form state validation. Instead of a fatal error or a standard Drupal block message, it triggers a custom <code>#type =&gt; &#39;html_tag&#39;</code> element that bypasses aggressive text filters to render a styled ⚠️ toast notification right where the user is looking.</p>
<h3>5. CSS Overrides</h3>
<p>Finally, I added highly specific CSS rules (using <code>!important</code> where necessary) to strip out default Drupal form and button margins. If you are making custom CSS in Drupal, chances are you have already done this : )</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783916694/drupal/gqivemly8oxvc2ggto79.png" alt="synced logs" /><figcaption>synced logs</figcaption></figure>
<h1>Changes we did in LLM Arena</h1>
<h2>The Unified User Experience</h2>
<p><strong>Restored Action Buttons:</strong> If you noticed missing icons on buttons like &quot;Remove&quot; or &quot;Fire Prompt,&quot; they are now fully restored! I integrated Font Awesome 6 icons directly into the buttons, complete with smooth hover animations and the clean Drupal Claro color palette. Earlier the icons were not there : )</p>
<p><strong>A Unified Preview Interface:</strong> Previewing a generated recipe in the LLM Arena now uses the exact same UI as the Main Chat Dashboard, ensuring a familiar experience everywhere in the app.</p>
<h2>Under the Hood: Technical Implementation</h2>
<p>Bringing the Arena up to speed required solving some quirks in Drupal&#39;s Form API and centralizing our data architecture.</p>
<h3>1. Outsmarting HTML <code>&lt;input&gt;</code> Tags for CSS Icons</h3>
<p>In Drupal, form action buttons are rendered as <code>&lt;input type=&quot;submit&quot;&gt;</code> tags. Because <code>&lt;input&gt;</code> is an HTML void element, it ignores CSS <code>::before</code> or <code>::after</code> pseudo-elements, which broke my injected Font Awesome icons.</p>
<p>To fix this without breaking Drupal’s Form API or AJAX:</p>
<p>I wrapped the submit inputs in a structural <code>#type =&gt; &#39;container&#39;</code> (<code>&lt;div&gt;</code>).</p>
<p>I moved the Font Awesome styling to this new wrapper.</p>
<p>I styled the inner <code>&lt;input&gt;</code> element to be visually transparent (<code>opacity: 0</code>) while stretching it to cover the styled wrapper, making it fully clickable.</p>
<h3>2. Centralizing Database Logic</h3>
<p>Previously, the Arena and Main Chat used entirely different mechanisms to save data. The Arena was bypassing our centralized <code>RecipeStorageService</code> and executing direct database inserts, leading to fragmented data and sync issues.</p>
<p>I refactored the data layer to fix this:</p>
<p><strong>Service Injection:</strong> I updated <code>ArenaService</code> to require <code>RecipeStorageService</code> via dependency injection.</p>
<p><strong>Streamlined Saving:</strong> I completely rewrote the <code>saveToDashboard()</code> method to route through <code>$this-&gt;storageService-&gt;logGeneration()</code> instead of performing raw DB queries.</p>
<p><strong>Clean Separation:</strong> I implemented a unique thread ID prefix (<code>arena_...</code>) and a distinct action type (<code>arena_benchmark</code>). This allows the Arena and Main Chat to share the exact same database tables without mixing up standard chat threads with Arena benchmarks.</p>
<p>By centralizing the storage logic, the codebase is much more maintainable, and our dashboard data is perfectly synchronized.</p>

<pre><code class="language-plaintext">tests/src/
├── Unit/                  # Fast, isolated tests with no Drupal bootstrap
├── Kernel/                # Integration tests with partial Drupal bootstrap
├── Functional/            # Full browser tests with complete Drupal installation
├── FunctionalJavascript/  # JavaScript interaction tests
└── Traits/                # Shared test helpers</code></pre>
<h1>Testing my way through it</h1>
<p>Lastly, I’ve been giving the AI Recipe Generator a lot of cosmetic love, polishing the UI, sharpening the presentation, and making sure the front-end looks delicious. But any chef will tell you that a beautiful plate doesn&#39;t matter if the kitchen is chaotic.</p>
<blockquote>
<p>Had taken quite a bit of refrence from AI Context (my goto place for refrence)</p>
</blockquote>
<p>This week, there are no new buttons or shiny features. Instead, I’ve been under the hood, deep-cleaning the backend engine to ensure it never chokes on an order.</p>
<h3>The Backend Recipe: Pure Isolation</h3>
<p>AI engines are brilliant, but they can be messy prep cooks. When they spit out malformed YAML strings or stumble over missing database states, the app breaks. To fix this, I built a rigid validation line using PHPUnit:</p>
<p><strong>The YamlServices Test:</strong> Powered by PHPUnit Data Providers, this acts like a strict health inspector. Every single generated YAML string is checked for syntax compliance before it ever touches runtime.</p>
<p><strong>The ContextExtractor Test:</strong> A true test of a chef is how they perform in an empty kitchen. I completely mocked out Drupal core services (like <code>ConfigFactory</code> and <code>EntityTypeManager</code>). The system can now map site context in total isolation, free from live database dependencies.</p>
<h3>The Secret Ingredient: Modern PHP 8 Attributes</h3>
<p>Out with the stale, in with the fresh. I stripped out the old-school docblock annotations and upgraded the suite to modern PHP 8 Attributes (like <code>#[CoversClass]</code> and <code>#[DataProvider]</code>). It’s cleaner, faster, and fully prepped for Drupal 11+.</p>
<h3>Next on the Menu</h3>
<p>The foundation is rock solid, but the prep work isn&#39;t done. Up next:</p>
<p>Baking Kernel tests for the <code>BenchmarkDashboardService</code> to map database logic.</p>
<p>Functional testing for the Recipe Generation forms.</p>
<p>Isolating remaining stateful tests with <code>#[RunTestsInSeparateProcesses]</code>.</p>

<blockquote>
<p>Thanks for reading &amp; catch you up next time :)</p>
</blockquote>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783916610/drupal/mscus5tnvcirugz4pia9.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal : Week 6 - The Halfway Line]]></title>
            <description><![CDATA[Week 6 of Google Summer of Code with Drupal. We have reached the halfway mark, overcoming previous challenges with Twig files and database formatting to make significant progress on the AI recipe generator project.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a4769664978da8200f2b029</link>
            <guid isPermaLink="false">6a4769664978da8200f2b029</guid>
            <pubDate>Fri, 03 Jul 2026 07:48:54 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomee to Week 6 of our journey!!</strong></p>
<p>We have finally crossed the half way mark of our journey, that we are going to go to. Last week we were in a bit of soup, had to rework a bit of twig files and format. This week we have covered a lot, most of the functions, databases etc.</p>
<p>Official Tracker: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3607160">Week 6</a></p>
<p>Glimpse of what we have built so far in our journey!</p>

<figure><iframe src="https://www.youtube.com/embed/8GxKKUelOHg" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063486/drupal/xqd6yq2suzasugbmko0r.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063492/drupal/h2zqlogbz200cpctexpa.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063571/drupal/tqf6me7pn8jkh2llrlvl.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063579/drupal/zsmxqprpdxudiarpbcol.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063679/drupal/eatvcyntnixoeilxlo99.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783063708/drupal/krxjfvtcvhqhsmzqdqme.png" alt="Drupal image" /></figure>
<blockquote>
<p>We have tried to cover all aspects rather than just one part of it. </p>
</blockquote>
<p>let&#39;s dive deep into Week 6!</p>

<h2>Getting things on track</h2>
<p>Our first issue was that the chat UI you can see was not visible, the functions were not working. So first I took one thing at a time, defined the structure, just the core one without any proper focus on UI just the feature should work &amp; data should be stored. </p>

<h2>The UI Foundation: Keep It Simple</h2>
<p>I started by establishing the base structure. I set up a single Twig file mapped to a custom Controller. For now, I kept the CSS <em>really, really bare</em>. The goal this week was purely structural and functional—making sure the data flows correctly before making it look pretty.</p>
<p>The core of the interface is the <strong>Preview Pane</strong> on the right side of the screen. This pane gives the user three main capabilities:</p>
<ol>
<li><strong>Preview the YAML:</strong> See exactly what the AI generated.</li>
<li><strong>Explain the Solution:</strong> Get a plain-text breakdown of what the recipe actually does.</li>
<li><strong>Retry / Refine the Prompt:</strong> Iterate on the generation if it missed the mark.</li>
</ol>
<p>To make the YAML editing experience native and smooth, I pulled in a new dependency: the <a href="https://www.drupal.org/project/yaml_editor">yaml_editor</a> module, which provides a great out-of-the-box editing experience.</p>
<h2>Validation &amp; Contextual Edits</h2>
<p>Generating code is only half the battle; ensuring it won&#39;t break a Drupal site is the other.</p>
<p>I added <strong>Validator buttons</strong> that run the generated code through the YAML parser service. This ensures the YAML structure is valid and strictly checks the schema for the required recipe components: <code>name</code>, <code>description</code>, <code>type</code>, <code>install</code>, and <code>config</code>.</p>
<p>I also implemented an <strong>Apply Edits</strong> button. This feature is particularly cool because it takes into account both the user&#39;s natural language feedback <em>and</em> any inline comments they leave directly inside the YAML editor. It packages that context and sends it back to the AI so the LLM understands exactly <em>where</em> and <em>why</em> it needs to refine the recipe.</p>
<h2>Exporting &amp; Saving Recipes</h2>
<p>Once a user has a valid, working recipe, they need to actually use it. I built out the functionality to either save the recipe directly to the server&#39;s file system or package it into a ZIP file for download.</p>
<p>Here is the implementation for the core file management logic:</p>

<pre><code class="language-php">  /**
   * Saves the generated YAML to a physical folder on the server.
   */
  public function submitSaveRecipe(array &amp;$form, FormStateInterface $form_state) {
    $yaml = $form_state-&gt;get(&#39;latest_yaml&#39;);

    if (empty($yaml)) {
      $this-&gt;messenger()-&gt;addWarning($this-&gt;t(&#39;There is no recipe to save yet.&#39;));
      return;
    }

    $folder_name = &#39;recipe_&#39; . date(&#39;Ymd_His&#39;);
    $directory = $this-&gt;rootPath . &#39;/recipes/custom/&#39; . $folder_name;

    try {
      $this-&gt;fileSystem-&gt;prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);

      $file_path = $directory . &#39;/recipe.yml&#39;;
      $this-&gt;fileSystem-&gt;saveData($yaml, $file_path, FileExists::Replace);

      $this-&gt;messenger()-&gt;addStatus($this-&gt;t(&#39;Recipe saved successfully to: @path&#39;, [&#39;@path&#39; =&gt; $file_path]));
    }
    catch (\Exception $e) {
      $this-&gt;messenger()-&gt;addError($this-&gt;t(&#39;Failed to save recipe: @message&#39;, [&#39;@message&#39; =&gt; $e-&gt;getMessage()]));
    }

    $form_state-&gt;setRebuild(TRUE);
  }

  /**
   * Generates a ZIP file on the fly and forces a browser download.
   */
  public function submitExportZip(array &amp;$form, FormStateInterface $form_state) {
    $yaml = $form_state-&gt;get(&#39;latest_yaml&#39;);

    if (empty($yaml)) {
      $this-&gt;messenger()-&gt;addWarning($this-&gt;t(&#39;There is no recipe to export.&#39;));
      return;
    }

    // 1. Safely determine the temporary directory.
    $drupal_temp = $this-&gt;fileSystem-&gt;realpath(&#39;temporary://&#39;);

    // If Drupal&#39;s temporary directory is not writable, fallback to system temp.
    $temp_dir = ($drupal_temp &amp;&amp; is_writable($drupal_temp)) ? $drupal_temp : sys_get_temp_dir();

    // Create a unique absolute file path for the ZIP.
    $zip_filename = $temp_dir . &#39;/recipe_export_&#39; . uniqid(&#39;&#39;, TRUE) . &#39;.zip&#39;;

    $zip = new \ZipArchive();

    if ($zip-&gt;open($zip_filename, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== TRUE) {
      $this-&gt;messenger()-&gt;addError($this-&gt;t(&#39;Could not initialize the ZIP file.&#39;));
      return;
    }

    $zip-&gt;addFromString(&#39;recipe.yml&#39;, $yaml);

    // 2. Check if close() actually succeeds (writes to disk).
    if (!$zip-&gt;close()) {
      $this-&gt;messenger()-&gt;addError($this-&gt;t(&#39;Failed to write the ZIP file to the server. Check permissions for: @dir&#39;, [&#39;@dir&#39; =&gt; $temp_dir]));
      return;
    }

    // 3. Force the browser download
    $response = new BinaryFileResponse($zip_filename);
    $response-&gt;setContentDisposition(
      ResponseHeaderBag::DISPOSITION_ATTACHMENT,
      &#39;ai_recipe_export.zip&#39;
    );

    // Automatically delete the temporary file after download completes.
    $response-&gt;deleteFileAfterSend(TRUE);

    // Bypass standard form rendering to output the file.
    $form_state-&gt;setResponse($response);
  }

</code></pre>
<h2>The Brains of the Operation: RecipeGenerationService</h2>
<p>To orchestrate all of this, the <code>RecipeGenerationService</code> acts as the central nervous system. It connects the Drupal AI provider, extracts the site context, and formats the ChatInputs depending on whether the user is generating a new recipe, asking for an explanation, or refining existing code.</p>
<p>One of the most important architectural decisions here was keeping the codebase DRY (Don&#39;t Repeat Yourself). If you look closely at the class below, you will notice that <code>generateRecipe</code>, <code>explainRecipe</code>, and <code>refineRecipe</code> all construct their specific prompt structures, but they all ultimately route their data through a <strong>single, unified function</strong>: <code>queryLlm()</code>.</p>
<p>By centralizing the actual handoff to the AI provider into one core method, the system is much easier to maintain, debug, and scale if we need to add new AI operations down the line.</p>

<pre><code class="language-php">&lt;?php

namespace Drupal\ai_recipe_generator\Service;

use Drupal\ai\AiProviderPluginManager;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\ai_recipe_generator\Utility\YamlProcessor;

/**
 * Service to generate Drupal Recipes using AI.
 */
class RecipeGenerationService {

  protected AiProviderPluginManager $aiProviderManager;
  protected ContextExtractor $contextExtractor;
  protected ConfigFactoryInterface $configFactory;

  public function __construct(AiProviderPluginManager $ai_provider_manager, ContextExtractor $context_extractor, ConfigFactoryInterface $config_factory) {
    $this-&gt;aiProviderManager = $ai_provider_manager;
    $this-&gt;contextExtractor = $context_extractor;
    $this-&gt;configFactory = $config_factory;
  }

  public function getProviderOptions(): array {
    $options = [];
    $definitions = $this-&gt;aiProviderManager-&gt;getDefinitions();

    foreach ($definitions as $provider_id =&gt; $definition) {
      try {
        $provider = $this-&gt;aiProviderManager-&gt;createInstance($provider_id);
        if ($provider-&gt;isUsable(&#39;chat&#39;)) {
          $options[$provider_id] = (string) $definition[&#39;label&#39;];
        }
      }
      catch (\Exception $e) {
        // Skip unconfigured providers.
      }
    }
    return $options;
  }

  public function getModelOptionsForProvider(string $provider_id): array {
    if (empty($provider_id)) {
      return [];
    }

    try {
      $provider = $this-&gt;aiProviderManager-&gt;createInstance($provider_id);
      if ($provider-&gt;isUsable(&#39;chat&#39;)) {
        return $provider-&gt;getConfiguredModels(&#39;chat&#39;);
      }
    }
    catch (\Exception $e) {
      // Skip if provider fails to instantiate.
    }
    return [];
  }

  private function buildChatInputRecipeYamlGeneration(string $user_prompt): ChatInput {
    $site_context = $this-&gt;contextExtractor-&gt;getSiteContextMap();
    $config = $this-&gt;configFactory-&gt;get(&#39;ai_recipe_generator.settings&#39;);
    $system_instructions = $config-&gt;get(&#39;system_prompt&#39;);
    $context_message = &quot;Active Site Context: &quot; . $site_context;

    return new ChatInput([
      new ChatMessage(&#39;system&#39;, $system_instructions . &#39; &#39; . $context_message),
      new ChatMessage(&#39;user&#39;, $user_prompt),
    ]);
  }

  private function buildChatInputRecipeExplanationGeneration(string $yaml_content): ChatInput {
    $prompt = &quot;Please explain the following Drupal Recipe YAML in simple, plain text. Do not output any code, just explain what it does. You may also reference parts of the code that need explanation in the response:\n\n&quot; . $yaml_content;

    return new ChatInput([
      new ChatMessage(&#39;user&#39;, $prompt),
    ]);
  }

  private function buildChatInputRecipeRefineGeneration(string $yaml_content, string $user_feedback): ChatInput {
    $prompt = &quot;The following is a Drupal Recipe YAML:\n\n&quot; . $yaml_content . &quot;\n\nPlease refine the YAML based on the user&#39;s feedback: &quot; . $user_feedback . &quot;. Ensure it remains valid and adheres to the schema. Also ensure that the user comments in the yaml and taken care of.&quot;;

    $site_context = $this-&gt;contextExtractor-&gt;getSiteContextMap();
    $config = $this-&gt;configFactory-&gt;get(&#39;ai_recipe_generator.settings&#39;);
    $system_instructions = $config-&gt;get(&#39;system_prompt&#39;);
    $context_message = &quot;Active Site Context: &quot; . $site_context;

    return new ChatInput([
      new ChatMessage(&#39;user&#39;, $prompt),
      new ChatMessage(&#39;system&#39;, $system_instructions . &#39; &#39; . $context_message),
    ]);
  }

  public function generateRecipe(string $user_prompt, string $provider_id, string $model_id): string {
    $input = $this-&gt;buildChatInputRecipeYamlGeneration($user_prompt);
    return YamlProcessor::normalize($this-&gt;queryLlm($provider_id, $model_id, $input));
  }

  public function explainRecipe(string $yaml_content, string $provider_id, string $model_id): string {
    $input = $this-&gt;buildChatInputRecipeExplanationGeneration($yaml_content);
    return $this-&gt;queryLlm($provider_id, $model_id, $input);
  }

  public function refineRecipe(string $yaml_content, string $user_feedback, string $provider_id, string $model_id): string {
    $input = $this-&gt;buildChatInputRecipeRefineGeneration($yaml_content, $user_feedback);
    return YamlProcessor::normalize($this-&gt;queryLlm($provider_id, $model_id, $input));
  }

  /**
   * THE CORE ENGINE: 
   * A single, unified function that handles all AI interactions.
   */
  private function queryLlm(string $provider_id, string $model_id, ChatInput $input): string {
    $provider = $this-&gt;aiProviderManager-&gt;createInstance($provider_id);
    $response = $provider-&gt;chat($input, $model_id);
    return $response-&gt;getNormalized()-&gt;getText();
  }
}

</code></pre>
<p>This setup ensures that whether a user is generating a recipe from scratch or iterating on a complex YAML file with custom comments, the active site context is always injected seamlessly and executed uniformly.</p>

<h3>The Context Conundrum: Why JSON:API Wasn&#39;t the Answer</h3>
<p>When you use AI to generate <code>recipe.yml</code> files, the generator is essentially flying blind unless you give it an architectural blueprint of the current Drupal site.</p>
<p>If the LLM doesn&#39;t know what already exists, it will try to generate recipes that recreate existing configuration entities (like a <code>field_image</code> or a <code>blog</code> content type). When you try to apply that recipe, it results in fatal errors and massive configuration collisions. The AI needed to know the state of the site, so I had to figure out the most efficient way to feed it that data.</p>
<p>My initial thought was to simply make an HTTP request using <code>\Drupal::httpClient()</code> to hit the local <code>/jsonapi</code> endpoint. JSON:API is great for exposing site data, right?</p>
<p>Yes, but for this specific use case, it was the wrong tool for the job.</p>
<h4>The Pivot: Internal Services Over HTTP</h4>
<p>Executing an external web request to a site’s <em>own</em> JSON:API endpoint while running code on that exact same environment adds unnecessary performance overhead. Furthermore, JSON:API is designed to return <em>content</em> (the actual data), whereas the AI only needed the <em>architecture</em> (the blueprints). Sending actual node data to the LLM would instantly bloat the payload and consume the entire context window.</p>
<p>Instead, I dropped the HTTP approach and utilized Drupal&#39;s internal services—specifically <code>EntityTypeManager</code> and <code>ConfigFactory</code>—to generate a highly optimized, lightweight structural map.</p>
<p>Here is how I structured the final data extraction to ensure recipe safety:</p>
<ol>
<li><strong>Performance &amp; Payload Size:</strong> By fetching only machine names, bundles, and entity types, the resulting JSON payload is incredibly lightweight and easily fits within standard AI context windows.</li>
<li><strong>Comprehensive Field Discovery:</strong> It is easy to just query <code>field.field.node.*</code>, but that ignores fields attached to taxonomy terms, media, or users. Instead, I queried <code>field.storage.*</code>. This captures <em>all</em> fields across the entire site, ensuring the AI can properly reuse existing field storage (a core Drupal best practice).</li>
<li><strong>Content vs. Config Entities:</strong> Drupal Recipes frequently interact with configuration entities (like roles, menus, and block placements). I updated the queries to explicitly map out both content and config entities so the AI has a 360-degree view of the system.</li>
</ol>
<h4>The Implementation: <code>getSiteContextMap()</code></h4>
<p>To handle this, I introduced a new backend method called <code>getSiteContextMap()</code>. It uses those internal services to build out three main arrays: a clean list of node bundles, a unique array of all field storage machine names, and a multidimensional map of all system entities categorized by their group.</p>
<p>Here is a look at how that blueprint generation comes together in the code:</p>

<pre><code class="language-php">  /**
   * Generates a lightweight architectural blueprint of the Drupal site.
   *
   * @return string
   * A JSON-encoded string mapping the site&#39;s architecture.
   */
  public function getSiteContextMap(): string {
    $context = [
      &#39;existing_content_types&#39; =&gt; [],
      &#39;existing_fields&#39; =&gt; [],
      &#39;all_entity_types&#39; =&gt; [
        &#39;content&#39; =&gt; [],
        &#39;config&#39; =&gt; [],
      ],
    ];

    // 1. Fetch existing content types (node bundles).
    $node_types = $this-&gt;entityTypeManager-&gt;getStorage(&#39;node_type&#39;)-&gt;loadMultiple();
    $context[&#39;existing_content_types&#39;] = array_keys($node_types);

    // 2. Fetch existing fields across all entity types via field storage.
    $field_storages = $this-&gt;entityTypeManager-&gt;getStorage(&#39;field_storage_config&#39;)-&gt;loadMultiple();
    foreach ($field_storages as $field) {
      $context[&#39;existing_fields&#39;][] = $field-&gt;getName();
    }
    // Ensure the array is unique and re-indexed.
    $context[&#39;existing_fields&#39;] = array_values(array_unique($context[&#39;existing_fields&#39;]));

    // 3. Map all entity types (Content and Config) and their respective bundles.
    $entity_types = $this-&gt;entityTypeManager-&gt;getDefinitions();
    foreach ($entity_types as $entity_type_id =&gt; $entity_type) {
      $group = $entity_type-&gt;getGroup();
      
      if (in_array($group, [&#39;content&#39;, &#39;config&#39;])) {
        $bundles = \Drupal::service(&#39;entity_type.bundle.info&#39;)-&gt;getBundleInfo($entity_type_id);
        $context[&#39;all_entity_types&#39;][$group][$entity_type_id] = array_keys($bundles);
      }
    }

    return json_encode($context);
  }
</code></pre>
<p>By passing the output of this method into the system prompt (which connects perfectly to the <code>RecipeGenerationService</code> built earlier), the AI now has a perfect, zero-overhead map of the site it is generating code for. No more fatal errors, no more configuration collisions.</p>

<h2>Last but not the least - documentation</h2>
<p>We finally did some detailed documentation utilizing the fucntion comments and all the things that we were writing. This will be helpful for a handover and for understand and explaining what function we need. Check it out here: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3607975">https://www.drupal.org/project/ai_recipe_generator/issues/3607975</a></p>
<p>So what I did was to generate a python script in the module folder, which scans the repo, then another one when generates a markdown file and a pdf file for export which can be suitable for our module.</p>
<p>You can checkout the detailed PDF here: <a href="https://git.drupalcode.org/project/ai_recipe_generator/-/blob/1.0.x/docs/Codebase_Architecture.pdf?ref_type=heads">https://git.drupalcode.org/project/ai_recipe_generator/-/blob/1.0.x/docs/Codebase_Architecture.pdf?ref_type=heads</a></p>

<p>Thanks a lot &amp; catch you up next week!!!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783069307/drupal/zimfg4koeivwkisv2azp.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal - Week 5: Debugging Our Way]]></title>
            <description><![CDATA[Week 5 of my Google Summer of Code journey with Drupal involved a complete rebuild of the project architecture to improve stability, UI responsiveness, and core functionality like ZIP exports and YAML editing.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a475f088adddd5bc5245924</link>
            <guid isPermaLink="false">6a475f088adddd5bc5245924</guid>
            <pubDate>Fri, 03 Jul 2026 07:04:40 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomme Back to Week 5!!</strong></p>
<p>Sometimes in software development, the best way forward is to burn it down and start over. Week 5 of my Google Summer of Code journey with Drupal was exactly that.</p>
<p>Official Tracker: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3605319">Week 5 Tracker</a></p>
<p>The original version of the project simply wasn&#39;t working as intended. The foundation was shaky, and patching it was taking more time than fixing the root cause. So, I made the tough call to hit the reset button. I went back to the drawing board to recreate the entire thing—starting from the ground up with fresh HTML and form templates.</p>
<p>This time, the architecture is much more robust, allowing me to finally push through the proper updates. Here is a look at what was on the agenda for the rebuild:</p>
<h3>UI &amp; Layout Refinements</h3>
<p>Getting the interface to feel native and clean was a huge priority this round.</p>
<ul>
<li><strong>Sidebar Enhancements:</strong> Completely revamped the CSS for the sidebar to make it cleaner and more responsive.</li>
<li><strong>Page Alignment:</strong> Fixed the overarching structural alignment of the page so the layout doesn&#39;t break under different states.</li>
</ul>
<h3>Core Feature Updates</h3>
<p>With the new templates in place, I started wiring up the core functionality that the AI module actually needs to be useful.</p>
<ul>
<li><strong>The &quot;Explain&quot; Prompt:</strong> Added the new &quot;Explain&quot; prompt type to the system.</li>
<li><strong>Markdown Rendering:</strong> Ensured that the explain history parses proper markdown and renders it cleanly for the user.</li>
<li><strong>Zip Export &amp; Import:</strong> Added the functionality to seamlessly export and import configurations via ZIP files.</li>
<li><strong>YAML Editor Swap:</strong> Decided to drop the old YAML preview and swap it out entirely with the much more robust <a href="https://www.drupal.org/project/yaml_editor">yaml_editor</a> module.</li>
</ul>
<h3>State &amp; History Fixes</h3>
<ul>
<li><strong>Editable Sidebar:</strong> Fixed the &quot;Edit Prompt&quot; sidebar issue, ensuring the state is actually editable and saves correctly.</li>
<li><strong>History Management:</strong> Overhauled the history functionality to ensure it tracks and retrieves past prompts reliably.</li>
</ul>
<blockquote>
<p><strong>Developer Note:</strong> A complete rewrite mid-project is always daunting, but the momentum it creates is undeniable. While Week 5 was all about laying this new groundwork and tackling these 8 core issues, the actual completion and polishing of many of these features spilled over into Week 6.</p>
</blockquote>
<p>Stay tuned for the Week 6 update, where all of these rebuilt components finally cross the finish line!</p>

<figure><iframe src="https://www.youtube.com/embed/8GxKKUelOHg" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1783062256/drupal/lyxoxjyuppzszjtoz7we.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x Drupal - Week 4 Building the UI]]></title>
            <description><![CDATA[Week 4 of my Google Summer of Code project with Drupal focused on UI design, establishing color schemes, and applying UX principles like Jakob's Law to create an intuitive AI Recipe Generator interface.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a37f5a24d826d18a926ebb0</link>
            <guid isPermaLink="false">6a37f5a24d826d18a926ebb0</guid>
            <pubDate>Sun, 21 Jun 2026 14:30:58 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Welcomeee to Week 4 - The UI Styling</strong></p>
<p>This week was pretty cool for building the UI &amp; getting started with that. Had defined the colour scheme, the master colours of the Drupal Page &amp; then started building the Figma for the same. This process was more towards thought and what the user would feel good using, what will enable him / her to use better. </p>
<p> A cool page for good UI practices is: <a href="https://www.instagram.com/synsation_/?hl=en">Synsation</a>. </p>
<p>Official Week 4 Tracker: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3601251">Week 4</a></p>
<p>Time to take a dive into depths of this week:</p>
<p>Take a look at Figma: <a href="https://www.figma.com/design/hHK10sy1CBstTQr2FYVGRg/Hrishikesh-Dalal-s-team-library?node-id=3313-15&t=vxwsfUv1bTm4scJV-1">UI Core</a></p>

<figure><iframe src="https://www.youtube.com/embed/vRku3DgQZ6c" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1782550053/drupal/sknprg8w6spxkgfwpece.png" alt="The UI" /><figcaption>The UI</figcaption></figure>
<h2>Design Principles</h2>
<p>In a video I watched by Synsation, she explained that using familiar user interfaces is incredibly helpful for users. Think about it: almost every e-commerce website puts the shopping cart in the top-right corner. This intentional placement ensures that when a user lands on a new site, they don&#39;t feel completely overwhelmed trying to figure out where things are.</p>
<p>This UX design concept is known as <strong>Jakob&#39;s Law</strong>.</p>
<blockquote>
<p><strong>Read More:</strong> You can explore the full concept at <a href="https://lawsofux.com/jakobs-law/">Laws of UX: Jakob&#39;s Law</a>.</p>
</blockquote>
<p>Then I had to make the master colour scheme, as the theme has to be uniform and match the Drupal Theme. So took colours from the default Drupal Theme: </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1782550859/drupal/jca2o4exlynb1owng3by.png" alt="Master Colours" /><figcaption>Master Colours</figcaption></figure>
<h1>Dissecting an AI-Powered Chat Interface in Drupal</h1>
<p>Tracing a request through a complex Drupal module can feel like unraveling a mystery. Recently, I mapped out the complete request/response flow of a custom AI Recipe Generator module. Instead of a monolithic block of code, the module is a great example of a decoupled, service-oriented architecture within Drupal.</p>
<h2>The Anatomy of the Three-Pane Layout</h2>
<p>The chat page is not a single file, but a composition of specialized parts orchestrated by a central controller.</p>
<p>When a request hits <code>ai_recipe_generator.routing.yml</code>, it is routed to the <code>RecipeChatController</code>. The controller&#39;s primary job is to assemble the page shell: it checks for a thread ID (generating one if necessary), builds the interactive form, prepares a starter preview panel, and loads recent threads.</p>
<p>The visual layout is handled by theme hooks defined in the <code>.module</code> file, splitting the UI into three distinct areas:</p>
<ul>
<li><strong>Left Sidebar (<code>recipe-chat-sidebar.html.twig</code>):</strong> Displays dynamic thread navigation and links to sibling pages (Dashboard, Arena, Settings).</li>
<li><strong>Middle Chat Area (<code>recipe-chat-form.html.twig</code>):</strong> The interactive core containing the prompt input, provider/model controls, and message history.</li>
<li><strong>Right Preview Panel (<code>ai-yaml-preview.html.twig</code>):</strong> A shared YAML preview component featuring validation badges, dependency checks, a copy button, and an edit-apply workflow.</li>
</ul>
<h2>The Lifecycle of a User Request</h2>
<p>The core interaction logic lives inside <code>RecipeChatForm.php</code>. Here is exactly what happens when a user clicks &quot;Send&quot;:</p>
<ol>
<li><strong>State Management:</strong> The form reads the prompt, selected AI provider, and model, appending the user&#39;s message to the in-memory history.</li>
<li><strong>AJAX Updates:</strong> If a user changes the AI provider, an AJAX callback (<code>updateModelDropdown</code>) instantly updates the model selector.</li>
<li><strong>Generation:</strong> The form passes the baton to the AI generation service to build the prompt and fetch the response.</li>
<li><strong>Validation &amp; Logging:</strong> The generated YAML is validated, logged to the database alongside benchmark data, and the form storage is updated.</li>
<li><strong>Rebuild:</strong> The page rebuilds to display the new chat bubble and the updated right-hand preview panel.</li>
</ol>
<p>Secondary interactions follow similar targeted paths. Clicking &quot;Retry&quot; runs <code>submitRetryForm</code> to regenerate using the last known parameters, while &quot;Apply Edits&quot; from the preview panel triggers <code>submitApplyEdits</code> to re-validate and log a variant record.</p>
<h2>Decoupling Logic: The Services Layer</h2>
<p>If everything lived inside the form class, the module would be a maintenance nightmare. Instead, the <code>ai_recipe_generator.services.yml</code> file wires up highly specialized services.</p>
<ul>
<li><strong><code>RecipeGenerationService</code>:</strong> Manages AI provider connections, model selection, builds the system prompt, and calls the AI API.</li>
<li><strong><code>ContextExtractor</code>:</strong> Gathers site context (like existing content types) and verifies YAML dependencies against installed modules.</li>
<li><strong><code>YamlServices</code>:</strong> Centralizes all YAML syntax and recipe schema validation.</li>
<li><strong><code>RecipeStorageService</code>:</strong> Handles database persistence, writing generation histories, storing edited variants, and fetching data for the sidebar.</li>
<li><strong><code>PreviewPanelBuilder</code>:</strong> Transforms raw YAML into the interactive render array seen in the right-hand pane.</li>
<li><strong><code>BenchmarkDashboardService</code>:</strong> Records benchmark data after every generation run for later analysis.</li>
</ul>
<h2>Beyond the Chat: The Module Ecosystem</h2>
<p>The true benefit of this service-oriented architecture is code reusability. Because the heavy lifting is handled by services and shared Twig components, the module easily supports several sibling pages:</p>
<ul>
<li><strong>RecipeGeneratorTestForm:</strong> A stripped-down, bare-bones generator. It reuses the provider/model pattern and YAML preview, but strips away the threaded chat UI.</li>
<li><strong>ArenaForm:</strong> A multi-model comparison tool. It leverages the same generation service but runs multiple models in a batch process, storing results in <code>tempstore</code> for side-by-side comparison.</li>
<li><strong>BenchmarkDashboardForm:</strong> The analytics wing. It skips generation entirely, instead reading the benchmark history to visualize performance.</li>
<li><strong>SettingsForm:</strong> A simple configuration page that stores the system prompt utilized by the generation service.</li>
</ul>

<h2>A Challenge</h2>
<p>There is one challenge that I had faced, while making the history section, I was not sure whether or not it was being updated or created. I had to directly interact with the SQL section. A few commands which proved that data was being stored.</p>

<pre><code class="language-bash">ddev drush sql-query &quot;SELECT id, thread_id, prompt, yaml_valid FROM ai_recipe_generator_history;&quot;</code></pre>
<pre><code class="language-bash">drush sql-cli</code></pre>
<p>Now the yaml code was being generated, which often had some modules to install. I created the different sections - already installed, to be installed, invalid / hallucinated modules names. In order to do this had to verify whether the modules given by ai even actually exist. </p>

<h1>How to Verify if a Drupal Module Exists: 5 Methods from the Community</h1>
<p>I recently asked the community for the best ways to programmatically validate whether a Drupal module exists. I received a lot of great insights, tracking down everything from Packagist endpoints to Drupal core APIs.</p>
<h2>Method 1: The Packagist Route</h2>
<p><em>Suggested by: deadm3n</em></p>
<p>The initial idea was to hit the standard Packagist endpoint to check for the package JSON:</p>
<p><em>GET <a href="https://repo.packagist.org/p2/%5Bvendor%5D/%5Bpackage%5D.json">https://repo.packagist.org/p2/[vendor]/[package].json</a></em></p>
<blockquote>
<p><strong>One Obs:</strong> This didn&#39;t quite work as expected for Drupal-specific modules : (. But was a good approach. </p>
</blockquote>
<h2>Method 2: Project Browser UI</h2>
<p><em>Suggested by: nixvcan</em></p>
<p>If you are looking for a visual or ecosystem-driven check, you can leverage the <strong>Project Browser</strong> module directly within Drupal, which lists available modules natively.</p>
<h2>Method 3: Drupal&#39;s Composer Endpoint &amp; Search APIs</h2>
<p><em>Suggested by: cmlara</em></p>
<p>This approach focuses on basic Composer operations. You can look up a specific package if you know its full name using Drupal&#39;s dedicated Packagist endpoint:</p>
<p><em>GET <a href="https://www.drupal.org/files/packages/8/p2/drupal/field_updater.json">https://www.drupal.org/files/packages/8/p2/drupal/field_updater.json</a></em></p>
<h3>Searching the Endpoint</h3>
<p>If you don&#39;t know the exact name, you can search the endpoint.</p>
<ul>
<li>Note: The standard Packagist spec typically uses <code>q</code> for queries, but Drupal currently uses <code>s</code>. Keep an eye on this, as it might change in the future.</li>
</ul>
<p><em>GET <a href="https://packages.drupal.org/8/search.json?s=seo">https://packages.drupal.org/8/search.json?s=seo</a></em></p>
<blockquote>
<p><strong>Update Status XML:</strong> You can also use the Update Status XML, but it relies strictly on the Drupal.org machine name (which might not always match the Composer package name and excludes sub-modules). This might be a bit of bottleneck. </p>
</blockquote>
<h2>Method 4: Local Site vs. Global Ecosystem</h2>
<p><em>Suggested by: sujal</em></p>
<p>We wanted to check if a module exists on the Drupal org not just if it there one our local!</p>
<p>If you just need to check your active site, the traditional Drupal approach works best. <a href="https://drupal.stackexchange.com/questions/188322/how-can-i-check-if-a-module-is-enabled">Read More</a></p>
<h2>Method 5: The JSON:API Approach (My Method)</h2>
<p><em>Discovered by: Myself</em></p>
<p>If you want to verify a module&#39;s existence globally on Drupal.org via code, you can query Drupal&#39;s official JSON:API for the project module content type:</p>
<p><em>GET <a href="https://www.drupal.org/jsonapi/node/project_module?filter%5Bfield_machine_name%5D=your_module_name">https://www.drupal.org/jsonapi/node/project_module?filter[field_machine_name]=your_module_name</a></em></p>
<ul>
<li><strong>Valid:</strong> The API returns a populated <code>data</code> array.</li>
<li><strong>Invalid:</strong> The <code>data</code> array returns empty.</li>
</ul>

<h2>Final Changes</h2>
<p>Finally after completing all the changes, comes the tough part the linting and styling errors : ). Went though quite a few iterations after which the code was completed. That was the best relief - cspell, phpcs, php unit  - all done!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1782107288/drupal/zilkyrov04e56urwhlpl.png" alt="The satisfaction" /><figcaption>The satisfaction</figcaption></figure>
<p>Last part was pushing the code into gitlab. One of the tasks if you saw the tracker was learning about the Merge Train. So this time while pushing the code finally pushed it using it. You can read more about it in my other blog.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1782112827/drupal/jebhv1xcikm1rjn4j3hx.png" alt="Merge Train" /><figcaption>Merge Train</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1782561271/drupal/jjgkxiudwjvibgcn6oho.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code x GSoC-  Merge Train & Auto Merges]]></title>
            <description><![CDATA[Week 4 of my GSoC journey: Exploring the concepts of Merge Trains and Auto Merges to streamline development workflows and prevent broken builds in busy repositories.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a36517e2dab42ed4069cbdb</link>
            <guid isPermaLink="false">6a36517e2dab42ed4069cbdb</guid>
            <pubDate>Sat, 20 Jun 2026 08:38:22 GMT</pubDate>
            <content:encoded><![CDATA[<p>While I was merging the code, I came across a term called the merge train. I was curious as too what it held, being maintainer for a repo is a totally different thing : )</p>
<h2>The Analogy: A Busy Airport Runway</h2>
<p>You are managing the runway of a super busy airport (the Main Branch of Code). Pilots (Developers) are trying to land their planes (Merge Requests).</p>
<h2>1. The Normal Merge (The Chaotic Runway)</h2>
<p>The air traffic control looks at a plane 20 km away, sees that its engines work fine, and says: &quot;You are cleared to land immediately.&quot;</p>
<p>The problem? Two minutes ago, another plane was cleared to land on that exact same runway. Because the controller didn&#39;t check what was currently happening on the ground, the two planes crash into each other.</p>
<ul>
<li>In Code: Your feature works fine alone. Another developer&#39;s feature works fine alone. But when you both merge directly into the main branch at the same time, they conflict and break the system.</li>
</ul>
<p>There have been countless cases where developers have pushed code simultaneously has it has broken down 😅. So we sometimes in live website have different stages like - Development, QA, Staging &amp; then finally - live production. </p>
<h2>2. The Merge Train (The Pre-Flight Staging Queue)</h2>
<p>Instead of letting planes land instantly, the airport sets up a strict, moving line on the taxiway.</p>
<p>Plane A gets in line. The tower runs a simulation: &quot;If Plane A lands on the empty runway, is it safe?&quot; Yes.
Plane B gets in line right behind it. The tower runs a simulation: &quot;Assuming Plane A lands safely, will Plane B be safe landing right behind it?&quot;
Plane C gets in line. The tower simulates: &quot;If A lands, and then B lands, is it safe for C to land?&quot;</p>
<p>The planes move down the track together like a train. They only get to touch the actual runway once the entire simulated sequence proves to be 100% safe.</p>
<p>This will help us so that nothing is in conflict. Super useful if there are many developers working on a project. In our case its just me, so might not be that useful.</p>
<h2>3. Warnings vs. Errors (The Safety Inspector)</h2>
<p>While the planes are in the Merge Train queue, a safety inspector walks by to check them.</p>
<p>An <strong>Error (Red Status)</strong>: The inspector finds a broken wing on Plane B. Plane B is immediately kicked out of the line. The tower quickly reruns the simulation to see if Plane C can safely follow Plane A instead.</p>
<p>A <strong>Warning (Orange Status</strong>): The inspector notices Plane B has a scuffed coat of paint or a loose reading light above Seat 12B. It’s not perfect, but it is not going to cause a crash. The inspector notes it down, but lets the plane stay in the train to land.</p>
<h2>4. Auto-Merge (The Valet Parking Attendant)</h2>
<p>Imagine you are a pilot, and you are currently 50 miles away from the airport. You know your plane is in perfect condition, but there is a 30-minute line to get into the staging queue. You don&#39;t want to sit at the controls doing nothing while you wait for your turn.</p>
<p>You engage Auto-Merge. This is like handing the keys to a valet attendant. You can go sleep in the back of the plane. The valet will automatically steer your plane into the back of the Merge Train the exact second the gate opens for you, without you needing to touch a single button.</p>
<p>This is our entire story of Merging &amp; Git. Untangling another vine in the open source jungle. </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781953734/drupal/tasubq3wu36u2wyyddyk.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Drupal x Google Summer of Code - Week 3: Reuse & Previews]]></title>
            <description><![CDATA[Week 3 of my GSoC journey with Drupal: I focused on building a reusable YAML extractor and validator utility to streamline AI response processing, along with minor UI improvements for the recipe generator.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a2f96118bbe21d5504f21c2</link>
            <guid isPermaLink="false">6a2f96118bbe21d5504f21c2</guid>
            <pubDate>Mon, 15 Jun 2026 06:05:05 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>WELLLLCOMMEE to Week 3 of this series!</strong></p>
<p>In the last week made quite a few structural changes that will help us. Have created the YAML extractor and parser which can extract the YAML component form any response of the LLM using Symfony to validate it. Made a Utility for this thereby making the code reusable. </p>
<p>Official Issue Tracker: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3594062">Week 3 Tracker</a></p>
<p>Let me now give me glimpse for this. </p>

<figure><iframe src="https://www.youtube.com/embed/7sH6pv2Vyns" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781504252/drupal/u7akwfg75woresztzmje.png" alt="The Output Preview &amp; Validator" /><figcaption>The Output Preview &amp; Validator</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781504306/drupal/boxwpkoc1vmuvigbb6zb.png" alt="UI Changes" /><figcaption>Tiny UI Changes</figcaption></figure>
<blockquote>
<p>Time to Dive Deep into this</p>
</blockquote>
<h2>YAML Validator &amp; Extractor</h2>
<p>Created a Utility folder under src and created a file named Yaml Processor. The concept of this file was simple, when you get an output from LLM, from whichever Service or Function you pull this from this will extract the YAML and give it to you.</p>
<blockquote>
<p>Here it uses this : use Drupal\Component\Serialization\Yaml</p>
</blockquote>
<p>This under the hood uses Symfony which helps to validate and extraction.  Symfony is the underlying PHP framework that powers modern Drupal ( this is just one of the usecase)</p>

<pre><code class="language-php">&lt;?php

namespace Drupal\ai_recipe_generator\Utility;

use Drupal\Component\Serialization\Yaml;

/**
 * Utility class to parse and extract YAML from AI responses.
 */
class YamlProcessor {

  /**
   * Cleans a response down to YAML text when possible.
   *
   * @param string $response
   *   The raw model response.
   *
   * @return string
   *   A normalized YAML string or the response with code fences removed.
   */
  public static function normalize(string $response): string {
    $parsed = self::extractAndParse($response);

    if (is_array($parsed)) {
      return trim(Yaml::encode($parsed));
    }

    $cleaned = preg_replace(&#39;/```(?:yaml|yml)?\s*|```/i&#39;, &#39;&#39;, $response) ?? $response;
    return trim($cleaned);
  }

  /**
   * Attempts to extract and validate YAML from a given string.
   *
   * @param string $response
   *   The raw string response.
   *
   * @return array|null
   *   The parsed YAML as an array, or null if no valid YAML could be extracted.
   */
  public static function extractAndParse(string $response): ?array {
    // First try parsing the entire response as YAML.
    try {
      $parsed = Yaml::decode($response);

      if (is_array($parsed)) {
        return $parsed;
      }
    }
    catch (\Exception $e) {
      // Not valid YAML, continue with extraction.
    }

    // Extract YAML from markdown code blocks.
    $pattern = &#39;/```(?:yaml|yml)?\s*(.*?)```/is&#39;;

    if (preg_match($pattern, $response, $matches)) {
      $yaml = trim($matches[1]);

      try {
        $parsed = Yaml::decode($yaml);

        if (is_array($parsed)) {
          return $parsed;
        }
      }
      catch (\Exception $e) {
        return NULL;
      }
    }

    return NULL;
  }

}
</code></pre>
<h2>Reusable YAML Previewer</h2>
<p>If you have seen my previous blog, the older YAML previewers they were not upto the mark. So improved that with having better parsers and lines. What I used for this was a template, using a html.twig format which helped to render properly anywhere, anyhow!</p>
<p>This is slightly difficult to think and then implement (tedious slightly) but since highly reusable, highly useful!</p>

<pre><code class="language-plaintext">{#
/**
 * @file
 * Template for the fluid YAML previewer component with line numbers.
 */
#}
&lt;div class=&quot;ai-yaml-preview-container&quot; {{ attributes }}&gt;
  &lt;div class=&quot;ai-yaml-preview-header&quot;&gt;
    &lt;span class=&quot;ai-yaml-preview-title&quot;&gt;{{ title|default(&#39;YAML Output Preview&#39;) }}&lt;/span&gt;
    
    &lt;button class=&quot;ai-yaml-copy-btn&quot; onclick=&quot;
      const container = this.closest(&#39;.ai-yaml-preview-container&#39;);
      const code = container.querySelector(&#39;code&#39;).innerText;
      navigator.clipboard.writeText(code);
      const originalText = this.innerText;
      this.innerText = &#39;Copied!&#39;;
      setTimeout(() =&gt; this.innerText = originalText, 2000);
    &quot;&gt;Copy YAML&lt;/button&gt;
  &lt;/div&gt;
  
  &lt;div class=&quot;ai-yaml-preview-body&quot;&gt;
    {# This empty div will be filled with numbers by the JS below #}
    &lt;div class=&quot;ai-yaml-line-numbers&quot; aria-hidden=&quot;true&quot;&gt;&lt;/div&gt;
    
    &lt;pre class=&quot;ai-yaml-preview-content&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;{{ yaml_string }}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

{# Inline script to generate line numbers based on the code length #}
&lt;script&gt;
  (function() {
    // Find all preview containers on the page
    const containers = document.querySelectorAll(&#39;.ai-yaml-preview-container&#39;);
    
    containers.forEach(container =&gt; {
      const codeBlock = container.querySelector(&#39;code&#39;);
      const linesContainer = container.querySelector(&#39;.ai-yaml-line-numbers&#39;);
      
      // Only process if it hasn&#39;t been filled yet
      if (codeBlock &amp;&amp; linesContainer &amp;&amp; linesContainer.innerHTML === &#39;&#39;) {
        // Split by newline to get total lines
        const lines = codeBlock.innerText.split(&#39;\n&#39;);
        
        // Handle the case where the AI leaves a trailing empty line at the end
        const lineCount = lines[lines.length - 1] === &#39;&#39; ? lines.length - 1 : lines.length;
        
        let numbersHTML = &#39;&#39;;
        for (let i = 1; i &lt;= Math.max(1, lineCount); i++) {
          numbersHTML += i + &#39;&lt;br&gt;&#39;;
        }
        linesContainer.innerHTML = numbersHTML;
      }
    });
  })();
&lt;/script&gt;</code></pre>
<h2>Code Reduction</h2>
<p>Let&#39;s understand the problem, we had 3 files for calling the LLM models &amp; providers. All of them had separate functions. So if I ever wanted to change anything, I would have to change in all the three files : (</p>
<p>So we changed the calling part. for 2 (ArenaService &amp; RecipeGenerationService) of them as they were the same replicas the third one at BenchmarkDashboardService was a different one. Clubbing them was fairly simple, just had to change the function calls. </p>

<h2>Started fixing the Pipeline</h2>
<p>Did the cspell checking and then the phpcs checking which was initially failing but then started fixing the same. For fixing the cspell introduced a file called: .cspell.json. This needed to have all the words in the document which I wanted to exclude form the checking.</p>
<p>Will write a separate blog on testing!</p>

<pre><code class="language-json">{
    &quot;version&quot;: &quot;0.2&quot;,
    &quot;language&quot;: &quot;en&quot;,
    &quot;words&quot;: [
        &quot;ai_recipe_generator&quot;,
        &quot;chartjs&quot;,
        &quot;crosshairs&quot;,
        &quot;dalal&quot;,
        &quot;fontawesome&quot;,
        &quot;hrishikesh&quot;,
        &quot;llm&quot;,
        &quot;llms&quot;,
        &quot;multichat&quot;,
        &quot;onclick&quot;,
        &quot;Starshot&quot;,
        &quot;tempstore&quot;,
        &quot;unconfigured&quot;,  
        &quot;xaxis&quot;,
        &quot;xmark&quot;,
        &quot;yaml&quot;,
        &quot;yaxis&quot;
    ],
    &quot;ignorePaths&quot;: [
        &quot;vendor/**&quot;,
        &quot;node_modules/**&quot;,
        &quot;*.min.js&quot;,
        &quot;*.min.css&quot;
    ]
}</code></pre>
<blockquote>
<p>Thank you &amp; catch you up next time!</p>
</blockquote>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781509663/drupal/rwbkegeqyhsy7ucahzmo.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing & Debugging Pipeline]]></title>
            <description><![CDATA[Learn how to streamline your Drupal 11 development by setting up a local testing environment that mirrors GitLab CI, including tips for managing CSpell and debugging pipeline failures.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a2ed73f6e1f25cdfe854be5</link>
            <guid isPermaLink="false">6a2ed73f6e1f25cdfe854be5</guid>
            <pubDate>Sun, 14 Jun 2026 16:30:55 GMT</pubDate>
            <content:encoded><![CDATA[<p>Difficult part of turning the PIPELINE GREEN. Testing the code is super important, lets debug our way through it : )</p>
<p>If you are developing custom modules for Drupal 11, you already know that passing Drupal’s strict GitLab CI pipeline is a formidable challenge. Between CI pipelines failing over a single misspelled variable, coding standard violations, and Kernel tests failing to find databases, local environment setup is notoriously frustrating.</p>
<p>Setting up a modern Drupal 11 testing and CI environment locally using DDEV doesn&#39;t have to be a nightmare of version conflicts and obscure database errors. By understanding how to leverage CSpell, utilize Drupal&#39;s built-in formatting tools, and bridge PHPUnit to your Docker containers, you can build a bulletproof local pipeline that perfectly mirrors Drupal&#39;s strict GitLab CI.</p>

<h2>Surviving CSpell (The Code Spell Checker)</h2>
<p>One of the most common reasons a Drupal pipeline fails is CSpell. It scans your code—including YAML files, comments, and variable names—for spelling errors. The problem? It frequently flags technical acronyms, tool names (like ddev), and specific machine names as &quot;misspelled.&quot;</p>
<p>Because DDEV includes Node.js, you can run CSpell locally via npx without installing it globally.</p>
<h3>Step 1: Run the Spell Checker</h3>

<pre><code class="language-bash">ddev exec npx cspell &quot;web/modules/custom/your_module/**/*&quot;

# In some cases, even without typing &quot;ddev exec npx&quot; the code will work</code></pre>
<h3>Step 2: Creating CSPELL file</h3>
<p>When CSpell flags a legitimate technical word, do not change your code. Instead, create a .cspell.json file in the absolute root of your project:</p>

<pre><code class="language-json">{
  &quot;version&quot;: &quot;0.2&quot;,
  &quot;language&quot;: &quot;en&quot;,
  &quot;ignorePaths&quot;: [
    &quot;vendor/&quot;,
    &quot;node_modules/&quot;,
    &quot;web/core/&quot;
  ],
  &quot;words&quot;: [
    &quot;ddev&quot;,
    &quot;phpcbf&quot;,
    &quot;phpcs&quot;,
    &quot;phpunit&quot;,
    &quot;yourcustomword&quot;
  ]
}</code></pre>
<h3>Step 3: Keep Your Dictionary Organized</h3>
<p>Sorting your .cspell.json file alphabetically keeps it clean and makes it much easier to manage as your module grows. You can do this quickly without manual sorting:</p>
<p><strong>Method A: The Quick VS Code Way (My preferred way)</strong></p>
<p>1). Open .cspell.json in VS Code.</p>
<p>2). Select only the lines containing the words inside the words array.</p>
<p>3). Open the Command Palette (Ctrl + Shift + P or Cmd + Shift + P).</p>
<p>4). Type Sort Lines Ascending and press Enter. Add back any missing commas if the trailing word shifted.</p>
<p><strong>Method B: The Command Line Way (Using jq)</strong>
If you want to sort the entire JSON file perfectly via the terminal, use jq. Install it inside your WSL terminal (sudo apt install jq), then run:</p>
<p>This targets the words array, sorts it, and overwrites the file cleanly without breaking the JSON structure.</p>

<pre><code class="language-bash">jq &#39;.words |= sort&#39; .cspell.json &gt; temp.json &amp;&amp; mv temp.json .cspell.json</code></pre>
<h2>Mastering PHP CodeSniffer (PHPCS) &amp; Beautifier</h2>
<p>Drupal has incredibly strict coding standards. A single missing space or incorrect docblock will fail the CI pipeline. To automate this, we need to install Drupal&#39;s core development tools.</p>
<h3>Step 1: Install Drupal Core Dev Tools (The Right Way)</h3>
<p>Before you can sniff or auto-format code, you need Drupal&#39;s development dependencies. However, Composer often blocks this due to historical security advisories in locked packages.</p>
<p>To fix this, edit your project root composer.json (not the core or module file) to allow the installation, and explicitly declare your Drupal version to prevent Composer from downloading outdated Drupal 8/9 packages. Add this audit configuration to bypass the locked security warnings:</p>

<pre><code class="language-json">&quot;config&quot;: {
  &quot;allow-plugins&quot;: {
    &quot;composer/installers&quot;: true,
    &quot;drupal/core-composer-scaffold&quot;: true,
    &quot;drupal/core-project-message&quot;: true
  },
  &quot;audit&quot;: {
    &quot;block-insecure&quot;: false
  }
}</code></pre>
<p>(Note: You may also need to loosen constraints if packages like drupal/coder conflict, e.g., &quot;drupal/coder&quot;: &quot;^8.3.30 || ^9.0&quot;).</p>
<p>Run the require command with your exact Drupal core version (e.g., 11.3.11):</p>

<pre><code class="language-bash">composer require --dev &quot;drupal/core-dev:11.3.11&quot; --with-all-dependencies</code></pre>
<h3>Step 2: Auto-Fix with PHPCBF</h3>
<p>Always run the PHP Code Beautifier and Fixer (PHPCBF) first. It acts like an auto-formatter, automatically correcting indentation, spacing, array syntax, and missing docblocks.</p>
<p>Run this command to safely auto-fix your entire custom module:</p>

<pre><code class="language-bash">ddev exec ./vendor/bin/phpcbf --standard=Drupal,DrupalPractice --extensions=php,module,inc,install,test,profile,theme,css,info,txt,md,yml web/modules/custom/your_module</code></pre>
<h3>Step 3: Manually Check with PHPCS</h3>
<p>After the auto-fixer runs, use PHP CodeSniffer (PHPCS) to catch logical formatting errors that a machine can&#39;t safely fix (e.g., variables that are declared but never used, or missing parameter descriptions).</p>

<pre><code class="language-bash">ddev exec ./vendor/bin/phpcs --standard=Drupal,DrupalPractice --extensions=php,module,inc,install,test,profile,theme,css,info,txt,md,yml web/modules/custom/your_module</code></pre>
<h2>Conquering PHPUnit and the Database Dilemma</h2>
<p>Unlike simple Unit tests, Drupal Kernel and Functional tests require a fully operational database to build temporary, miniaturized versions of your site. If you are using DDEV and executing commands from your host machine (like WSL or macOS terminal), PHPUnit will fail because it cannot see inside the DDEV database container.</p>
<p>Since we already installed the drupal/core-dev dependencies in Phase 2, here is how to bridge the connection.</p>
<h3>Step 1: Configure phpunit.xml for DDEV</h3>
<p>PHPUnit needs to know how to talk to DDEV. Copy Drupal&#39;s phpunit.xml.dist to create a phpunit.xml file in your project root, and ensure the <env> variables under the <php> block look exactly like this</p>

<pre><code class="language-php">&lt;php&gt;
    &lt;ini name=&quot;error_reporting&quot; value=&quot;32767&quot; /&gt;
    &lt;ini name=&quot;memory_limit&quot; value=&quot;-1&quot; /&gt;
    &lt;env name=&quot;SIMPLETESTBASEURL&quot; value=&quot;https://your-project.ddev.site&quot; force=&quot;true&quot; /&gt;
    &lt;env name=&quot;SIMPLETEST_DB&quot; value=&quot;mysql://db:db@db/db&quot; /&gt;
    &lt;env name=&quot;BROWSERTESTOUTPUTBASE_URL&quot; value=&quot;https://your-project.ddev.site&quot; /&gt;
  &lt;/php&gt;</code></pre>
<p>Why this matters: mysql://db:db@db/db is the default internal DDEV database credential string. It tells PHPUnit to route directly to the active Docker container.</p>
<h3>Step 2: Run the Tests Inside DDEV</h3>
<p>If you run ./vendor/bin/phpunit from your standard terminal, it will fail with a socket error. You must tell DDEV to execute the command inside the web container.</p>

<pre><code class="language-bash">ddev exec ./vendor/bin/phpunit ./web/modules/custom/your_module/tests</code></pre>
<blockquote>
<p>Pro-Tip for Kernel Tests: If your test throws a ServiceNotFoundException, it means your custom module depends on another module that isn&#39;t loaded in the test environment. Ensure you declare all dependencies in your test class:</p>
</blockquote>

<pre><code class="language-php">protected static $modules = [&#39;yourmodule&#39;, &#39;dependentmodule&#39;];</code></pre>
<p>This is all, catch you up next time! </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1787051022/drupal/yjeaxpwa8ha1beoobabt.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Drupal: Schema's & Databases]]></title>
            <description><![CDATA[Learn how Drupal manages database schemas using .install files and hook_schema() instead of raw SQL, illustrated with a practical AI Recipe Generator module example.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a2d8f1fcb0f513275eaa3f7</link>
            <guid isPermaLink="false">6a2d8f1fcb0f513275eaa3f7</guid>
            <pubDate>Sat, 13 Jun 2026 17:10:55 GMT</pubDate>
            <content:encoded><![CDATA[<p>Whenever you want to use the database to store something, or if you want to have some custom information, a database will always be your go to option :)</p>
<p>Drupal has a really interesting way for databases. </p>
<p>So rather than writing the direct SQL statements, which would be tedious and boring, it uses a <strong>.install file</strong> to handle this. This translates into a proper query that we are going to use, which is useful for storing structured information. </p>
<p>Let us go through this with help of an example.</p>

<pre><code class="language-plaintext">&lt;?php

/**
 * @file
 * Install, update and uninstall functions for the AI Recipe Generator module.
 */

/**
 * Implements hook_schema().
 */
function ai_recipe_generator_schema() {
  $schema[&#39;ai_recipe_generator_benchmarks&#39;] = [
    &#39;description&#39; =&gt; &#39;Stores historical benchmark metrics for AI providers.&#39;,
    &#39;fields&#39; =&gt; [
      &#39;id&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;serial&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;description&#39; =&gt; &#39;Primary Key.&#39;,
      ],
      &#39;provider&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;varchar_ascii&#39;,
        &#39;length&#39; =&gt; 64,
        &#39;not null&#39; =&gt; TRUE,
        &#39;default&#39; =&gt; &#39;&#39;,
      ],
      &#39;model&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;varchar_ascii&#39;,
        &#39;length&#39; =&gt; 64,
        &#39;not null&#39; =&gt; TRUE,
        &#39;default&#39; =&gt; &#39;&#39;,
      ],
      &#39;prompt&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;text&#39;,
        &#39;size&#39; =&gt; &#39;big&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;description&#39; =&gt; &#39;The user prompt submitted to the AI.&#39;,
      ],
      &#39;response&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;text&#39;,
        &#39;size&#39; =&gt; &#39;big&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;description&#39; =&gt; &#39;The raw generated text/YAML from the LLM.&#39;,
      ],
      &#39;latency&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;int&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;description&#39; =&gt; &#39;Total response time in milliseconds.&#39;,
      ],
      &#39;yaml_valid&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;int&#39;,
        &#39;size&#39; =&gt; &#39;tiny&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;default&#39; =&gt; 0,
        &#39;description&#39; =&gt; &#39;1 if clean or recovered via sanitizer, 0 if completely unparsable.&#39;,
      ],
      &#39;schema_compliant&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;int&#39;,
        &#39;size&#39; =&gt; &#39;tiny&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;default&#39; =&gt; 0,
        &#39;description&#39; =&gt; &#39;1 if all required recipe keys are present.&#39;,
      ],
      &#39;timestamp&#39; =&gt; [
        &#39;type&#39; =&gt; &#39;int&#39;,
        &#39;not null&#39; =&gt; TRUE,
        &#39;default&#39; =&gt; 0,
      ],
    ],
    &#39;primary key&#39; =&gt; [&#39;id&#39;],
    &#39;indexes&#39; =&gt; [
      &#39;provider_model&#39; =&gt; [&#39;provider&#39;, &#39;model&#39;],
    ],
  ];

  return $schema;
}</code></pre>
<p>Here as you can see the name is ai_recipe_generator.install (i.e. name of your module)</p>
<p>First let&#39;s understand what is a hook_schema().</p>
<p>hook_schema() is a specific function that allows a module to define its own custom database tables. It serves as a blueprint or map that tells Drupal exactly how to structure data storage for that module.</p>
<p>Now the next thing that would come to your mind is - What does this do anyways?</p>
<h2>The ORM</h2><p>It acts like a ORM - Object Relational Mapper. Let&#39;s us take a step back and understand what an ORM is. </p>
<blockquote>
<p>The Object-Oriented World (Your Code): Written in languages like PHP, Python, or JavaScript, where data is treated as interconnected objects with properties and methods.</p>
</blockquote>
<blockquote>
<p>The Relational World (Your Database): Written in SQL (MySQL, PostgreSQL), where data is stored in rigid, flat tables consisting of rows and columns.</p>
</blockquote>
<p>So to simply put</p>
<p><strong>Without an ORM</strong>:  Manually write the SQL queries</p>
<p><strong>With an ORM</strong>: System writes SQL queries for you.</p>
<p>The ORM helps you write queries lightning fast &amp; good ORMs have security out of the box. The only disadvantage however is that they add another layer of abstraction which can sometimes slow down the speed (in case of a bloated SQL).</p>

<p>Coming back to the .install file.</p>
<p>We can say that</p>
<p>A Recipe class maps to recipe table</p>
<p>$recipe-&gt;title maps to a title column</p>
<p>Also helps to decide the NOT NULL fields &amp; Primark Keys.</p>
<p>Further this can automatically handles table creation + deletion on installing &amp; uninstalling. Simple isn&#39;t it?</p>

<h2>Some good toknowinfo</h2><p><strong>Standard Documentation Name:</strong> <code>hook_install()</code>
<strong>What you write for your module:</strong> <code>ai_recipe_generator_install()</code>
<strong>When Drupal runs it:</strong> Runs after the database table is created, perfect for inserting default data.</p>
<p><strong>Standard Documentation Name:</strong> <code>hook_uninstall()</code>
<strong>What you write for your module:</strong> <code>ai_recipe_generator_uninstall()</code>
<strong>When Drupal runs it:</strong> Runs when removing the module, perfect for clearing out configuration settings.</p>
<p><strong>Standard Documentation Name:</strong> <code>hook_update_N()</code>
<strong>What you write for your module:</strong> <code>ai_recipe_generator_update_10001()</code>
<strong>When Drupal runs it:</strong> Runs when a developer executes database updates on a live site.</p>

<p>The different types of keys in schemas.</p>
<h3>Table-level keys</h3>
<ul>
<li><code>description</code>: A short explanation of what the table stores and its purpose.</li>
<li><code>fields</code>: The list of columns that make up the table.</li>
<li><code>primary key</code>: The column(s) used to uniquely identify each record.</li>
<li><code>unique keys</code>: The column combinations that must contain unique values.</li>
<li><code>foreign keys</code>: The relationships between this table and other tables.</li>
<li><code>indexes</code>: The columns that are indexed to improve query performance.</li>
</ul>
<h3>Field specification keys</h3>
<ul>
<li><code>description</code>: A brief explanation of what the field represents.</li>
<li><code>type</code>: The kind of data the field stores, such as text or numbers.</li>
<li><code>mysql_type</code>: A custom data type definition specifically for MySQL.</li>
<li><code>pgsql_type</code>: A custom data type definition specifically for PostgreSQL.</li>
<li><code>sqlite_type</code>: A custom data type definition specifically for SQLite.</li>
<li><code>serialize</code>: Indicates whether the field value is stored in serialized format.</li>
<li><code>size</code>: Defines the expected size range of the data stored in the field.</li>
<li><code>not null</code>: Specifies whether the field is required and cannot be empty.</li>
<li><code>default</code>: The value automatically assigned when none is provided.</li>
<li><code>length</code>: The maximum number of characters allowed in text fields.</li>
<li><code>unsigned</code>: Restricts numeric values to non-negative numbers.</li>
<li><code>precision</code>: The total number of digits allowed in a numeric value.</li>
<li><code>scale</code>: The number of digits allowed after the decimal point.</li>
<li><code>binary</code>: Enables case-sensitive storage and comparison for text fields.</li>
</ul>
<h3>Foreign key specification keys</h3>
<ul>
<li><code>table</code>: The name of the table being referenced.</li>
<li><code>columns</code>: The mapping between local columns and referenced columns.</li>
</ul>

<p>Sources</p>
<p><a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21database.api.php/group/schemaapi/11.x">Drupal Schema</a></p>
<p><a href="https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21database.api.php/function/implementations/hook_schema/11.x">Functions in Drupal Schema</a></p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781413444/drupal/ujkq9n25ftgikrtegrbq.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Summer of Code (GSoC) x Drupal: Week 2 - Mapping the Dashboards]]></title>
            <description><![CDATA[A weekly recap of GSoC Week 2 with Drupal, focusing on building the LLM Benchmarking Dashboard and LLM Arena to evaluate model performance, latency, and accuracy.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a2d645f2a057c0c604fe02c</link>
            <guid isPermaLink="false">6a2d645f2a057c0c604fe02c</guid>
            <pubDate>Sat, 13 Jun 2026 14:08:31 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>WELCOMMMEEEE to another episode of Weekly Recap of my project</strong></p>
<p>This week we have built two things: LLM Benchmarking Dashboard &amp; LLM Arena to show you which ones are the best. Both of these will be super useful while building our final product. </p>
<p>The Offical Issue Tracker: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3593030">Week 2 Issues Tackled</a>
Let me give you a glipmse of what our output will look like. </p>

<figure><iframe src="https://www.youtube.com/embed/FWhz2f81urQ" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781359578/drupal/njlkdbbcehma9d6ynwqn.png" alt="Dashboard" /><figcaption>Dashboard (Part 1)</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781359606/drupal/eviouu8wsj4qyeptvtb9.png" alt="Dashboard " /><figcaption>Dashboard (Part 2)</figcaption></figure>
<h2>The Benchmarking Dashboard</h2><p>In order to capture the true essence of which model performs the best in the long run, had made a LLM benchmarking dashboard. Someone just by looking at charts can capture which model is most suitable for their usecase. </p>
<blockquote>Metrics that provided the overall picture</blockquote><p><strong>1). The Top Banner</strong>: It had the total runs so far, along with the average response time and the average accuracy for the recipe generated. </p>
<p><strong>2). The Visuals</strong>: Any dashboard is incomplete without a good visual. The first graph which shows the latency for the previous runs mapped across models is useful for finding the trend for model hits. The second graph is for using a bar graph for visualising the model avg. latency to compare which model is the faster as compared to others. Used the Chart module for the same. </p>
<p><strong>3). Key Metric Table</strong>: This is the detailed table for per prompt summary. It covers: <em>the provider, the model, the speed, whether the YAML is valid or not, whether the Schema proper or not as per Drupal Symfony, &amp; finally tells us about the accuracy along with timestamps</em>.</p>
<p>What is useful in this is that you can filter it along with sort the columns in ascending or descending order. Is if further <strong>colour coded</strong> so that you can quickly view the page at a glance. </p>
<p>Things I used to build this:</p>
<ul>
  <li>Chart.js / Drupal Chart Module: To render the responsive, interactive graphs.</li>
  <li>Custom Database Schema (.install): To reliably save and query the historical benchmarking data.</li>
  <li>Drupal AJAX API: To ensure the table filters and dynamic sorting update seamlessly.</li>
  <li>Font Awesome Library: To have cool icons in the dashboard</li>
</ul>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781359687/drupal/wy3fh91hwqzyz2losvre.png" alt="LLM Arena" /><figcaption>LLM Arena</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781359705/drupal/bfw3ernwat1hecyakw4f.png" alt="Drupal image" /></figure>
<h2>The LLM Arena </h2><p>This was particularly fun to build as this was for comparing the different responses. You get to type the custom prompt and then fire it across all the different models and providers. </p>
<blockquote>
<p>You can have all the different models &amp; providers, there is NO LIMIT!!</p>
</blockquote>
<p>Further these is also an option to store the responses in the database so that they reflected in the schema. There is also a delete button for the same. If you want to remove some model-provider.  Further you will see a very familiar screen that, while firing the prompts, it will look like new module install screen, this is because of the Batch API Screen. </p>
<p>Sources I used:</p>
<ol>
  <li>AJAX API: Model - Provider dropdown</li>
  <li>Batch API: For firing all the prompts together</li>
  <li>Font Awesome Icons: For the icons there</li>
</ol>
<h2>Laying out the Structure</h2>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781366121/drupal/ibcpl1lpkc6phhbhchid.png" alt="System Diagram" /><figcaption>System Diagram </figcaption></figure>
<p>Just like a good Drupal Practice (have to mention), have used AI to generate the image above. </p>
<h3>1. Client (User Browser) Layer</h3>
<p>Handles the user interface and visual presentation.</p>
<ul>
<li><p><strong>User (Arena/Dashboard Interface):</strong> Renders the comparison forms, interactive data tables, and metrics dashboards.</p>
</li>
<li><p><strong>Frontend Components:</strong></p>
</li>
<li><p><strong>JavaScript:</strong> Uses <code>Chart.js</code> to render interactive line and bar charts for model latency trends.</p>
</li>
<li><p><strong>Icons:</strong> Integrates <strong>Font Awesome</strong> for immediate visual cues (success/failure statuses).</p>
</li>
<li><p><strong>CSS:</strong> Uses <code>css/arena.css</code> and <code>css/dashboard.css</code> via <code>.libraries.yml</code> for custom, polished layouts.</p>
</li>
<li><p><strong>Client-Server Communication:</strong></p>
</li>
<li><p><strong>AJAX API:</strong> Controls non-blocking form updates, dynamic filtering, and column sorting without page reloads.</p>
</li>
<li><p><strong>Batch API (Client-Side):</strong> Drives the real-time progress bar (similar to the module installation screen) during execution.</p>
</li>
</ul>
<h3>2. Drupal Application Logic Layer</h3>
<p>The PHP backend that processes requests and orchestrates module logic.</p>
<ul>
<li><strong>Form API (<code>ArenaForm.php</code>, <code>BenchmarkDashboardForm.php</code>):</strong> Handles form submissions and handles backend execution triggers.</li>
<li><strong>Services Layer (<code>ArenaService.php</code>, <code>BenchmarkDashboardService.php</code>):</strong> Houses decoupled, reusable core logic registered via <code>ai_recipe_generator.services.yml</code>.</li>
<li><strong>API Integration Engine:</strong></li>
<li><strong>Drupal Batch API:</strong> Splits concurrent LLM requests into isolated chunks to prevent gateway timeouts and server lockups.</li>
<li><strong>Drupal AJAX API:</strong> Manages seamless, lightweight data passing between backend services and frontend components.</li>
</ul>
<h3>3. Data &amp; External Integrations Layer</h3>
<p>Manages persistent storage and outbound network requests.</p>
<ul>
<li><strong>Local Database (<code>.install</code>):</strong> Uses a custom schema defined in the install file to save historical run stats, latency, timestamps, and schema validation flags.</li>
<li><strong>External LLM APIs:</strong> Handles outbound calls to external models (OpenAI, Anthropic, Gemini). Isolated by the Batch API so a timeout on one provider doesn&#39;t crash the session.</li>
</ul>

<blockquote>
<p>Thanks a lot for reading!!</p>
</blockquote>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1781367505/drupal/yxmclfawvcdjd6xz702d.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Drupal x GSoC: Week 1 - Building the Base]]></title>
            <description><![CDATA[In the first week of my GSoC journey with Drupal, I focused on building the project foundation, setting up the module structure, and configuring essential routing, services, and form files for the AI Recipe Generator.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a2116768ded1b4d1690c224</link>
            <guid isPermaLink="false">6a2116768ded1b4d1690c224</guid>
            <pubDate>Thu, 04 Jun 2026 06:08:54 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>WELLLLLLCOMMEE To Week 1 of GSoC x Drupal.</strong></p>
<p>Over the first week my focus was on establishing a proper base for the project. This was particularly fun as you can see your project come to life. These are some other chapters of the journal that you should definitely read!</p>
<p>I created a project: <a href="https://blog.hrishikeshdalal.dev/drupal/6a1bbb83a9efe6ea5bb97c16">Creating a Module Project on Drupal (NO CODE) (With BONUS Section)</a></p>
<p>I learnt about <a href="https://blog.hrishikeshdalal.dev/drupal/6a200d7d5f3958f4a28782d7">PIRLS - Drupal&#39;s Core .yml files</a></p>
<p>Before you proceed any further, you should have a drupal project set up. If not, you can check it out here: <a href="https://blog.hrishikeshdalal.dev/drupal/6a21075f8d7f810de39a3084">Installing Drupal</a></p>
<p>Have added a bit more changes too, you can check it our here: <a href="https://www.drupal.org/project/ai_recipe_generator/issues/3592993">Week 1 Official Tracker</a></p>

<figure><iframe src="https://www.youtube.com/embed/2IcKTPBoyqc" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780549566/drupal/fgbvaxq71maydgairfiy.png" alt="Week 1 Issues Tackled" /><figcaption>Week 1 Issues Tackled</figcaption></figure>
<p>Let&#39;s zoom out and see what we have built. UI might not be that great yet but that was not the focus of the first week.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780557278/drupal/fdy8j8sr3yd6p15nse3r.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780557387/drupal/pmsx6pjzanp3mvpneaie.png" alt="Drupal image" /></figure>
<p>You should clone the basic repo of the module and paste it in: <strong>web/modules/custom/ai_recipe_generator</strong>.</p>
<p>You might need to create this folder &amp; add all the files. We use custom as we haven&#39;t made a deployment of the project yet. So once I did this I made. Now I have added ai_recipe_generator.info.yml, ai_recipe_generator_services.yml, ai_recipe_generator.routing.yml. I have made a blog on these files and what do these mean, you can check that out too. </p>
<p>I then created 2 things in src folder - Form &amp; Services. </p>
<p>So we have two pages as you saw above. Let&#39;s understand the routing or the file urls</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780557856/drupal/bihlzh3zc9xi3fhuvead.png" alt="Our File Structure" /><figcaption>Our File Structure</figcaption></figure>
<pre><code class="language-yaml">ai_recipe_generator.test_form:
  path: &#39;/admin/config/development/ai-recipe-test&#39;
  defaults:
    _form: &#39;\Drupal\ai_recipe_generator\Form\RecipeGeneratorTestForm&#39;
    _title: &#39;AI Recipe Generator (Test UI)&#39;
  requirements:
    _permission: &#39;administer site configuration&#39;

ai_recipe_generator.settings:
  path: &#39;/admin/config/ai-recipe/settings&#39;
  defaults:
    _form: &#39;\Drupal\ai_recipe_generator\Form\SettingsForm&#39;
    _title: &#39;AI Recipe Generator Settings&#39;
  requirements:
    _permission: &#39;administer site configuration&#39;</code></pre>
<p>Each entry has the following</p>
<p>1 The URL Path</p>
<p>2 The Form File which it should render</p>
<p>3 The title of the Page</p>
<p>4 Permission Requirements</p>

<hr>
<p>We have dependecies as well. This module as of now depends on the AI Module, Gemini Provider Module as well as the Key Module for managing all the keys. You have to install and enable all these modules for the AI Recipe Generator to function. This is actually cool as we are using the Drupal Ecosystem to do our tasks. </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780559026/drupal/dlej7qami6d1p4xzcqjv.png" alt="Flow Diagram" /><figcaption>Flow Diagram</figcaption></figure>
<h3>Phase 1: The Setup (Settings Form)</h3>
<p>Before a user can generate a recipe, an administrator sets the rules.</p>
<ol>
<li><strong>Setting the Ground Rules:</strong> An admin visits the &quot;AI Recipe Generator Settings&quot; form and enters the <strong>System Instructions</strong> (e.g., &quot;You are a senior Drupal developer...&quot;).</li>
<li><strong>Saving to the Database:</strong> When the admin clicks &quot;Save configuration,&quot; Drupal uses its configuration service (<code>@config.factory</code>) to safely store these instructions in the database for later use.</li>
</ol>
<h3>Phase 2: The User Request (Test UI Form)</h3>
<p>This is where the actual generation happens.</p>
<ol start="3">
<li><strong>User Input:</strong> A user opens the &quot;AI Recipe Generator (Test UI)&quot; form. They select their preferred <strong>AI Provider</strong> (like Gemini) and type exactly what they want to build into the <strong>Recipe Prompt</strong> box.</li>
<li><strong>Triggering the AJAX Call:</strong> The user clicks the <strong>Generate Recipe</strong> button. Instead of reloading the whole webpage, the browser sends a quiet, background message (an <strong>AJAX Request</strong>) to the Drupal server containing the user&#39;s prompt and their chosen AI provider.</li>
</ol>
<h3>Phase 3: The Backend Brain (Drupal Services)</h3>
<p>Once Drupal receives the AJAX request, it hands the work over to the custom services defined in your <code>.yml</code> file.</p>
<ol start="5">
<li><strong>Gathering the Pieces:</strong> The main service (<code>RecipeGenerationService</code>) goes to work collecting three crucial pieces of information:</li>
</ol>
<ul>
<li><strong>The Rules:</strong> It pulls the saved &quot;System Instructions&quot; using <code>@config.factory</code>.</li>
<li><strong>The Context:</strong> It asks the <code>ContextExtractor</code> service to pull any relevant background information about your specific Drupal site.</li>
<li><strong>The Request:</strong> It takes the user&#39;s specific &quot;Recipe Prompt&quot; from the AJAX call.</li>
</ul>
<ol start="6">
<li><strong>Talking to the AI:</strong> The <code>RecipeGenerationService</code> combines the rules, the context, and the user&#39;s request into one massive prompt. It then uses the <code>@ai.provider</code> service to send this combined package over the internet to the external AI (e.g., Gemini).</li>
</ol>
<h3>Phase 4: The Result (AJAX Response)</h3>
<ol start="7">
<li><strong>Receiving the Recipe:</strong> The external AI processes the prompt, generates the Starshot Recipe YAML file, and sends it back to your Drupal server.</li>
<li><strong>Updating the Screen:</strong> Drupal packages this generated recipe into an <strong>AJAX Response</strong> and sends it back to the user&#39;s browser. The browser reads this response and instantly displays the new recipe on the screen, completing the process without ever forcing the user to refresh the page.</li>
</ol>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780575149/drupal/jc0s5ilgchhaxo1kvveq.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Installing Drupal - Why its one of the most easiest installation]]></title>
            <description><![CDATA[A step-by-step technical guide on how to easily install and set up a Drupal project using Composer and DDEV for local development and module integration.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a21075f8d7f810de39a3084</link>
            <guid isPermaLink="false">6a21075f8d7f810de39a3084</guid>
            <pubDate>Thu, 04 Jun 2026 05:04:31 GMT</pubDate>
            <content:encoded><![CDATA[<p>Installing Drupal can be a task or it can be easy based on how you go about it. This is my guide on  how I set up Drupal Core for module integrations and product development.</p>

<figure><iframe src="https://www.youtube.com/embed/zeSB2W8j3GY" title="YouTube video" allowfullscreen></iframe></figure>
<h2> How I set up a Drupal Project</h2><p>This is the bare minimum as it allows us to TEST &amp; SEE the progress in real time. But setting this up is often challenging for many of us. Especially venturing into first time integrations. I have broken it down step by step so that it becomes easier for you. </p>
<p>You might but AI for the steps but trust me it may guide you wrong 😭</p>
<blockquote>
<p>You will need ddev &amp; git installed on your computer</p>
</blockquote>
<p><strong>Step 1: Creating a project</strong></p>
<p>Just be this step we can set up a sample project without any configurations. Here intead of <em>drupal_sandbox</em>, your name can be anything. Literally anything!</p>

<pre><code class="language-bash">composer create-project drupal/recommended-project drupal_sandbox
cd drupal_sandbox</code></pre>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780551130/drupal/dgddptherobvlfafedkr.png" alt="Ouput" /><figcaption>Output should look like this</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780551175/drupal/eqj0ppysns3pu6db9tvt.png" alt="End of the process" /><figcaption>End should look something like this</figcaption></figure>
<p><strong>Step 2: Making the ddev config file</strong></p>
<p>This file is responsible for handling your environment under the hood. Press ENTER after running this command. It takes care of the following:</p>
<p><strong>1) The Engine Type (type):</strong> Tells the system what framework you are using (like Drupal 11). DDEV instantly optimizes the Nginx web server and PHP settings specifically for that framework&#39;s unique code structure.</p>
<p><strong>2) The Front Door (docroot):</strong> Defines the exact folder that is exposed to the internet (like the web/ folder). It acts as a security barrier, blocking public access to your raw backend code, configuration files, and third-party dependencies.</p>
<p><strong>3) The Toolbox (php_version, db_type):</strong> Locks in the exact version numbers for your programming language, database engine, and runtime tools so your local server perfectly matches your production server.</p>
<p><strong>4) The Autopilot (hooks):</strong> Contains a list of custom terminal commands that you want executed automatically at specific moments—like running database migrations or flushing caches right after the containers turn on.</p>

<pre><code class="language-bash">ddev config</code></pre>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780551824/drupal/iiq9f2voap9fwene2gy5.png" alt="Output " /><figcaption>Output after running command</figcaption></figure>
<h2>Step 3: Start it</h2><p>Now you should run ddev start. Chances are it might throw an error :(</p>

<pre><code class="language-bash">ddev start</code></pre>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780552034/drupal/gw9iyd8k6ccrea3r6qic.png" alt="Output" /><figcaption>After Running ddev start</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780552079/drupal/hgr8devtyxregfim1nwm.png" alt="Error" /><figcaption>Error Message</figcaption></figure>
<p>This typically happens due to port mapping issue. Now a quick fix around this is that you can change the port mapping in the .ddev/config.yml file at the line number 47 &amp; 48. Update it with these lines and then do the restart.</p>

<pre><code class="language-yaml">router_http_port: 8080  
router_https_port: 8443</code></pre>
<pre><code class="language-bash">ddev restart</code></pre>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780552492/drupal/kpt9jaty0fulipaxblig.png" alt="Drupal image" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780552529/drupal/l3aecpjlph0e9ppyx8s4.png" alt="Now it will run 🎊🎊" /><figcaption>Now it will run 🎊🎊</figcaption></figure>
<h2>Step 4: Specifications Selection</h2><p>Select Save &amp; Continue for most of it. Now when prompted for the email, username &amp; password. Type something that you remember. I usually type: admin (for username &amp; password)</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780552807/drupal/wv2c97jt58ubjahekpc3.png" alt="Important step" /></figure>
<h1>Congrats!</h1><p>You have finally installed Drupal! Now you can start contributing here as well. </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780553732/drupal/c0ykii4w1srdt3pysglp.png" alt="Into Drupal" /></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780553754/drupal/uolna9jhqvwaznvsa0zb.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[PIRLS - Drupal's Core .yml files]]></title>
            <description><![CDATA[Learn about Drupal's core .yml files, including info, routing, and services, explained simply using a restaurant analogy to help you understand module configuration.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a200d7d5f3958f4a28782d7</link>
            <guid isPermaLink="false">6a200d7d5f3958f4a28782d7</guid>
            <pubDate>Wed, 03 Jun 2026 11:18:21 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you have opened some repository, chances are that you have worked on some of them! Now they are a lot, let us learn them in a simple way one by one.</p>
<p>Say you are a restraunt owner, starting you the amazing food chain called - &quot;More Food, Less Money&quot;. You attract a lot of hungry customers who want quality food. So to start and scale up, you have to do the following things</p>
<ol>
<li><p>Get your Business License.</p>
</li>
<li><p>Decide on the Host &amp; Menu. </p>
</li>
<li><p>Bing new staff &amp; Kitchen Appliances</p>
</li>
<li><p>Set up good Decor &amp; Ambiance. <em>Ik you love an asthetic place ;)</em></p>
</li>
<li><p>Establish for a keycard system for security.</p>
</li>
</ol>

<blockquote>
<p>Here let&#39;s call it ai_chef the module</p>
</blockquote>

<h2>The Business Liscence -  [module].info.yml</h2><p>Before you can open, you need a license. This tells the city (Drupal) your restaurant&#39;s name, its description, and what basic utilities (dependencies) it requires to function. Without this, the city doesn&#39;t even know your restaurant exists.</p>

<pre><code class="language-yaml">name: &#39;AI Vegetarian Recipe Generator&#39;
type: module
description: &#39;Integrates with the Gemini API to generate custom vegetarian recipes.&#39;
core_version_requirement: ^10 || ^11
package: Custom AI
dependencies:
  - drupal:key</code></pre>
<h2>The Host & Menu - [module].routing.yml</h2><p>When a customer asks for a table or a specific dish (a URL request like /about-us), the host uses this file to direct them to the exact right table or kitchen station (the PHP Controller) that will serve them.</p>

<pre><code class="language-yaml">ai_chef.menu_generator:
  path: &#39;/admin/kitchen/generate-recipe&#39;
  defaults:
    _controller: &#39;\Drupal\ai_chef\Controller\RecipeController::buildMenu&#39;
    _title: &#39;Generate New Recipes&#39;
  requirements:
    _permission: &#39;generate veg recipes&#39;</code></pre>
<h2>The Kitchen Staff & Appliances - [module].services.yml</h2><p>You don&#39;t buy a new oven every time someone orders a pizza. This file registers your heavy-duty, reusable tools (PHP classes) and connects them to the main power grid (Dependency Injection). It ensures your module can efficiently share resources with the rest of Drupal.</p>

<pre><code class="language-yaml">services:
  ai_chef.gemini_service:
    class: Drupal\ai_chef\Service\GeminiRecipeService
    arguments: [&#39;@http_client&#39;, &#39;@config.factory&#39;]</code></pre>
<h2>The Decor & Ambiance - [module].libraries.yml</h2><p>This manages your paint colors, lighting, and music (CSS and JavaScript). You don&#39;t want to pay for a mariachi band in the quiet dining room; this file lets you define groups of styles/scripts and load them only on the specific pages that need them.</p>

<pre><code class="language-yaml">kitchen_ambiance:
  version: 1.0
  css:
    theme:
      css/restaurant-theme.css: {}
  js:
    js/gemini-integration.js: {}
  dependencies:
    - core/drupal
    - core/jquery</code></pre>
<h2>The Keycard System - [module].permissions.yml</h2><p>This dictates security. It defines the specific digital locks on your doors (e.g., &quot;Access the VIP lounge&quot; or &quot;Edit the main menu&quot;) so the site administrator can hand out the right keycards to specific staff roles.</p>

<pre><code class="language-yaml">generate veg recipes:
  title: &#39;Generate Vegetarian Recipes&#39;
  description: &#39;Allows staff to use the Gemini API to create new menu items.&#39;
  restrict access: true</code></pre>
<h1>A cool way to remember this?</h1><p>You struggle to remember all these things as this is a lot of juggling for you, :(</p>
<p>But don&#39;t worry, your best friend suggests you something to help you:</p>
<p>To remember the five main .yml files, think of stringing together a necklace of PIRLS (Pearls).</p>
<p>P - Permissions ([module].permissions.yml)</p>
<p>I - Info ([module].info.yml)</p>
<p>R - Routing ([module].routing.yml)</p>
<p>L - Libraries ([module].libraries.yml)</p>
<p>S - Services ([module].services.yml)</p>

<p><em>Some sources</em></p>
<p><a href="drupal.org/docs/develop/creating-modules">Main Module Development Guide</a></p>
<p><a href="drupal.org/node/2000204">Understanding info.yml</a></p>
<p><a href="drupal.org/docs/drupal-apis/routing-system">Routing &amp; Controllers (routing.yml)</a></p>
<p><a href="drupal.org/docs/drupal-apis/services-and-dependency-injection">Services &amp; Dependency Injection (services.yml)</a></p>
<p><a href="drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview">Asset Management (libraries.yml)</a></p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780488980/drupal/zqiai6xphrhqcufleqgt.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding your first piece of code to Drupal Project!]]></title>
            <description><![CDATA[Learn how to set up your Drupal project codebase, initialize a Git repository, and push your first commit following Drupal's version control guidelines.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a1bbbde588c949600e9725f</link>
            <guid isPermaLink="false">6a1bbbde588c949600e9725f</guid>
            <pubDate>Sun, 31 May 2026 04:41:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Setting up the codebase on Drupal is <strong>EASY!</strong> Only you need a little bit of patience. Let&#39;s dive deep into it.</p>
<p>So previously we had set up a project. Now we are going to initialise the git repo and add a few changes. First if you have created your project, you can move on into the Version Control section. This will contain instructions of what we should do further. </p>
<p>This was my link: <a href="https://www.drupal.org/project/ai_recipe_generator/git-instructions">Version Control</a></p>

<figure><iframe src="https://www.youtube.com/embed/Ai0n_9vG_Ig" title="YouTube video" allowfullscreen></iframe></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780320368/drupal/cckksop4jlggh3e2mfvz.png" alt="Version Control " /><figcaption>Version Control</figcaption></figure>
<pre><code class="language-bash">mkdir ai_recipe_generator

cd ai_recipe_generator

git init

git checkout -b 1.0.x

echo &quot;AI Recipe Generator&quot; &gt; README.txt

git add README.txt

git commit -m &quot;Initial commit.&quot;

git remote add origin git@git.drupal.org:project/ai_recipe_generator.git

git push origin 1.0.x</code></pre>
<p>They are the explicit instructions of what we are going to do.</p>
<blockquote>
<p>Copying without understanding is useless. Let&#39;s go through each step one by one. </p>
</blockquote>

<table>
<thead>
<tr>
<th>Command</th>
<th>What it does</th>
<th>Why we do it</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>mkdir ai_recipe_generator</code></strong></td>
<td>Creates a new directory named <code>ai_recipe_generator</code>.</td>
<td>Allocates a dedicated local space for your project files.</td>
</tr>
<tr>
<td><strong><code>cd ai_recipe_generator</code></strong></td>
<td>Moves your terminal path into the new folder.</td>
<td>Ensures all future commands execute inside the project context.</td>
</tr>
<tr>
<td><strong><code>git init</code></strong></td>
<td>Initializes a hidden local <code>.git</code> repository.</td>
<td>Turns the plain folder into a version-controlled project.</td>
</tr>
<tr>
<td><strong><code>git checkout -b 1.0.x</code></strong></td>
<td>Creates and switches to a new branch named <code>1.0.x</code>.</td>
<td>Bypasses standard <code>main</code>/<code>master</code> to match Drupal&#39;s release branch rules.</td>
</tr>
<tr>
<td><strong><code>echo &quot;AI...&quot; &gt; README.txt</code></strong></td>
<td>Generates a <code>README.txt</code> file with project text.</td>
<td>Creates an initial asset to track, as Git cannot track empty folders.</td>
</tr>
<tr>
<td><strong><code>git add README.txt</code></strong></td>
<td>Stages the file to the Git index.</td>
<td>Explicitly marks the new file to be included in the next snapshot.</td>
</tr>
<tr>
<td><strong><code>git commit -m &quot;Text&quot;</code></strong></td>
<td>Saves the staged file with a historic log message.</td>
<td>Establishes the official baseline/first snapshot of your codebase.</td>
</tr>
<tr>
<td><strong><code>git remote add origin &lt;url&gt;</code></strong></td>
<td>Links your local Git setup to the Drupal.org repository.</td>
<td>Establishes the secure target destination path on Drupal&#39;s servers.</td>
</tr>
<tr>
<td><strong><code>git push origin 1.0.x</code></strong></td>
<td>Uploads the local branch and commit to the server.</td>
<td>Publishes your code online so your mentors can access and review it.</td>
</tr>
</tbody></table>

<p>Post this we had committed 3 more files of out own, which are equally crucial for the module development. </p>
<p><strong>1 ai_recipe_generator.module.info</strong></p>
<p><strong>2 composer.json</strong></p>
<p><strong>3 .gitignore</strong></p>
<p><em>Resources</em></p>
<ol>
<li>Module Creation &amp; .info.yml Documentation
<a href="https://www.drupal.org/docs/develop/creating-modules">Creating Custom Modules (Main Hub):</a> This is the central landing page for all module development tutorials in modern Drupal, covering everything from routing to creating custom blocks.</li>
</ol>
<p><a href="https://www.drupal.org/docs/develop/creating-modules/let-drupal-know-about-your-module-with-an-infoyml-file">Defining the .info.yml File:</a> This page provides the exact specifications, required properties, and core version requirement syntax for your module&#39;s metadata.</p>
<ol start="2">
<li>Composer Documentation for Modules
<a href="https://www.drupal.org/docs/develop/using-composer/add-a-composerjson-file">Adding a composer.json File to a Module:</a> This explains exactly why and how to add a composer.json file to your custom or contributed module, including the composer init steps specifically tailored for Drupal package types.</li>
</ol>

<pre><code class="language-yaml">name: AI Recipe Generator
description: Creates recipes using AI.
package: AI

type: module
core_version_requirement: ^10.5 || ^11.2

dependencies:
  - ai:ai</code></pre>
<pre><code class="language-json">{
    &quot;name&quot;: &quot;drupal/ai_recipe_generator&quot;,
    &quot;description&quot;: &quot;Creates recipes using AI.&quot;,
    &quot;type&quot;: &quot;drupal-module&quot;,
    &quot;license&quot;: &quot;GPL-2.0-or-later&quot;,
    &quot;authors&quot;: [
        {
            &quot;name&quot;: &quot;Hrishikesh Dalal&quot;,
            &quot;email&quot;: &quot;204620-hrishikesh-dalal@users.noreply.drupalcode.org&quot;
        }
    ],
    &quot;require&quot;: {}
}
</code></pre>
<h3>Somewhere where I struggled and made mistakes</h3><p>If you have seen the video, you would know I had made a mistake initializing the git repo 😅. I made a commit to the main brand before setting up the 1.0.x which is crucial for creating forks and deployments.  Well it sucked but it was a great source of learning. </p>
<p><strong>Understanding the Concept of Remote in Git</strong></p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780466304/drupal/ykvmgxf2ae8mky69ymg0.png" alt="Git Remote" /><figcaption>Git Remote</figcaption></figure>
<p>Think of Git <strong>Remotes</strong> as a <strong>Cloud Backup or a Shared Office.</strong></p>
<p>Imagine you are writing a book on your own private desk at home (<strong>Local Repository</strong>). You can write, delete, and organize pages however you want.</p>
<p>However, your publisher and co-authors are in a different building. To show them your work, you have a <strong>Shared Drafting Table</strong> (<strong>Remote Repository</strong>) in their office.</p>
<p><strong>1. A &quot;Remote&quot;:</strong> It&#39;s just a bookmark or a &quot;saved contact&quot; in your computer&#39;s address book that tells Git exactly where that Shared Table is located on the internet.</p>
<p><strong>2. Origin:</strong> This is just the default nickname for that Shared Table. (But you could call it anything, backup, publish etc)</p>

<p><strong>Managing Remotes: The Commands</strong></p>
<p>Sometimes you need to change where your code is going (e.g., commit to a different fork or repository).</p>
<p><strong>1. Check your current remotes</strong></p>
<p>Before changing anything, see where your project is currently &quot;talking&quot; to.</p>
<p><em>The <code>-v</code> stands for &quot;verbose.&quot; It shows you the URLs for fetching (downloading) and pushing (uploading).</em></p>

<pre><code class="language-bash">git remote -v</code></pre>
<p><strong>2. Change a remote&#39;s URL</strong></p>
<p>If the server location changes, but you want to keep the same nickname (<code>origin</code>), use this command:</p>

<pre><code class="language-bash">git remote set-url origin https://git.drupalcode.org/issue/ai_recipe_generator-3592994.git</code></pre>
<blockquote>
<p><strong>When to use:</strong> Use this if you accidentally typed the wrong URL during setup or if the project moved to a new server.</p>
</blockquote>

<p><strong>3. Add a second remote</strong></p>
<p>You can have more than one! You might have one for a private backup and one for the public Drupal project.</p>

<pre><code class="language-bash">git remote add backup https://git.drupalcode.org/issue/ai_recipe_generator-000001.git</code></pre>
<ol start="4">
<li>Rename a remote</li>
</ol>
<p>If you don&#39;t like the name <code>origin</code> and want to call it something else: (here drupal_server)</p>

<pre><code class="language-javascript">git remote rename origin drupal_server</code></pre>
<p><strong>5. Remove a remote</strong></p>
<p>If you want to cut ties with a specific server:</p>

<pre><code class="language-javascript">git remote remove backup
</code></pre>
<blockquote>
<p><strong>Pro-Tip:</strong> If you ever get an error saying &quot;Remote origin already exists,&quot; it means your project is already linked to a server. Use <code>set-url</code> to update it instead of <code>add</code>.</p>
</blockquote>

<h1>Eventually....</h1><p>So after taking a short detour, we finally pushed our initial code to gitlab! 🎉🎉</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780469509/drupal/uyzpyjdvdaginxkooibj.png" alt="Final Code Push" /><figcaption>Final Code Push</figcaption></figure>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating a Module Project on Drupal (NO CODE) (With BONUS Section)]]></title>
            <description><![CDATA[Learn how to create a Drupal Module Project without writing any code. This guide covers the different project types on Drupal and the step-by-step creation process, perfect for GSoC beginners.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a1bbb83a9efe6ea5bb97c16</link>
            <guid isPermaLink="false">6a1bbb83a9efe6ea5bb97c16</guid>
            <pubDate>Sun, 31 May 2026 04:39:31 GMT</pubDate>
            <content:encoded><![CDATA[<p>Hey there!</p>
<p>Welcome to my GSoC journey where in I am going to take you through an adventure of events. This blog is for the Level 1. Or rather I should saying learning about how to &quot;make the dish&quot;. We are going to create our very own Drupal Module Project!! </p>
<p>I will also append a Youtube link for the same if it does suit you better. Lets get into it!</p>

<figure><iframe src="https://www.youtube.com/embed/z7AmnRiPOso" title="YouTube video" allowfullscreen></iframe></figure>
<h1>Step 1: Understanding what all types of Projects you can create</h1><p>When you head over to this site for project creation - <a href="https://www.drupal.org/docs/develop/managing-a-drupalorg-theme-module-or-distribution-project/creating-a-new-project/how-to-create-a-new-project">Drupal Project Create</a>. You will have 4 options. </p>
<p>Drupal offers a few different project types:</p>
<p><strong>1. Module project:</strong> Contains PHP code that extends Drupal or adds new features.</p>
<p><strong>2. Theme project:</strong> Changes the look and feel of a Drupal site.</p>
<p><strong>3. General project:</strong> Contains code, but isn&#39;t a module, theme, or specific type.</p>
<p><strong>4. Community project</strong>: Non-code projects used to manage groups or camps.</p>
<p>Since our aim is to build an AI Recipe Generator that adds brand-new functionality to the site, selecting the Module project was the clear choice.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780307515/drupal/jny9ogiki6x5mddhw8kc.png" alt="Types of Drupal Projects" /><figcaption>Types of Drupal Projects</figcaption></figure>
<h1>Step 2: Have Git on your computer</h1><p>The absolute bare minimum as all the code changes everything should be done by this. But if you reading this and are a techie, you must already be having git : )</p>

<h1>Step 3: Creating a project</h1><p>Now when you go over this link: <a href="https://www.drupal.org/project/add">Project Creation</a>, you will have a ton of options. Let us go through each one of them one by one. </p>
<ul>
<li><strong>Drupal core:</strong> The foundational software that runs the entire Drupal platform.</li>
<li><strong>Distribution project:</strong> An all-in-one starter kit that bundles Drupal with pre-selected features to quickly launch a specific type of website.</li>
<li><strong>Community project:</strong> An organizational space for managing Drupal groups, events, and people rather than website code.</li>
<li><strong>General project:</strong> A catch-all category for miscellaneous code, tools, and libraries that don&#39;t fit into the standard module or theme categories.</li>
<li><strong>Module project:</strong> A functional plugin that adds new features or changes how your website works.</li>
<li><strong>Theme project:</strong> The design package that controls the colors, layout, and overall visual appearance of your website.</li>
<li><strong>Theme Engine project:</strong> The behind-the-scenes system that translates your theme&#39;s visual design into something the Drupal platform can understand.</li>
<li><strong>Translation project:</strong> An outdated archive for language translations, which are now managed on a separate, dedicated website.</li>
</ul>

<h1>Step 4: The Nitty Gritties</h1>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780309249/drupal/xzmdvudsel6amte9tgpf.png" alt="All Changes" /><figcaption>All the things you can do in a snap</figcaption></figure>
<p>Now when you go to the next page, there is this huge form that you need to fill. Let me take you across each filed one by one.</p>
<h3>The Basic Identity</h3>
<p><strong>Name: AI Recipe Generator</strong></p>
<ul>
<li><strong>What it is:</strong> The project&#39;s human-readable title.</li>
<li><strong>Why I chose it:</strong> It clearly tells users exactly what the tool does right away.</li>
</ul>
<p><strong>Short name: <code>ai_recipe_generator</code></strong></p>
<ul>
<li><strong>What it is:</strong> The machine-readable system name used for URLs and codebases.</li>
<li><strong>Why I chose it:</strong> It matches the readable name using standard Drupal convention (lowercase and underscores) for consistency.</li>
</ul>
<p><strong>Project type: Full project</strong></p>
<ul>
<li><strong>What it is:</strong> Defines if it&#39;s a temporary &quot;Sandbox&quot; or an official &quot;Full project.&quot;</li>
<li><strong>Why I chose it:</strong> To get end-to-end control and official release packaging for this GSoC module.</li>
</ul>
<h3>Status and Classification</h3>
<p><strong>Maintenance status: Actively maintained</strong></p>
<ul>
<li><strong>What it is:</strong> Shows the community how much attention the project gets.</li>
<li><strong>Why I chose it:</strong> I will be actively monitoring, updating, and managing this code for the next 12 weeks and beyond.</li>
</ul>
<p><strong>Development status: Under active development</strong></p>
<ul>
<li><strong>What it is:</strong> Indicates the codebase&#39;s current maturity.</li>
<li><strong>Why I chose it:</strong> The module is currently being built from the ground up and evolving daily.</li>
</ul>
<p><strong>Module categories &amp; Ecosystem: Artificial Intelligence (AI)</strong></p>
<ul>
<li><strong>What it is:</strong> Tags used to organize and filter Drupal projects.</li>
<li><strong>Why I chose it:</strong> Since the module relies on generative LLMs, this ensures the right audience finds it.</li>
</ul>
<p><strong>Dependencies: AI module</strong></p>
<ul>
<li><strong>What it is:</strong> Other modules required for this to work.</li>
<li><strong>Why I chose it:</strong> We are leveraging the existing Drupal AI core module to securely handle API configurations and process prompts.</li>
</ul>
<h3>Issue Tracking and Releases</h3>
<p><strong>Components: Code, Documentation, Miscellaneous, User interface</strong></p>
<ul>
<li><strong>What it is:</strong> Categories for issue queue tickets.</li>
<li><strong>Why I chose it:</strong> Granular tags keep the project organized and make bug triage much easier.</li>
</ul>
<p><strong>Enable issue tracker: Checked</strong></p>
<ul>
<li><strong>What it is:</strong> Allows the community to report bugs and submit patches.</li>
<li><strong>Why I chose it:</strong> Open source thrives on collaboration, and this is essential for community contributions.</li>
</ul>
<p><strong>Version options: Tagged releases, Development branch releases</strong></p>
<ul>
<li><strong>What it is:</strong> How Drupal packages the code for download via Composer.</li>
<li><strong>Why I chose it:</strong> It gives users the choice between a stable &quot;Tagged&quot; release or the bleeding-edge &quot;Development&quot; version.</li>
</ul>

<p><em>The Description that I put is below.</em></p>

<pre><code class="language-html">&lt;p&gt;The AI Recipe Generator is a module designed to accelerate site building by utilizing Artificial Intelligence to generate Drupal Recipes dynamically.&lt;/p&gt;

&lt;p&gt;By integrating with the Drupal AI ecosystem, this tool allows developers and site builders to input natural language prompts and automatically generate the necessary configuration, module dependencies, and structure required for a functional Drupal Recipe.&lt;/p&gt;

&lt;h3 id=&quot;module-project--features&quot;&gt;Features&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;Integrates directly with the Drupal AI module to process prompts.&lt;/li&gt;
  &lt;li&gt;Dynamically generates &lt;code&gt;recipe.yml&lt;/code&gt; files and associated configurations.&lt;/li&gt;
  &lt;li&gt;Streamlines the creation of standard site features (e.g., blog setups, event content types) without manual YAML writing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;module-project--post-installation&quot;&gt;Post-Installation&lt;/h3&gt;
&lt;p&gt;Once installed, you will configure the generator through the AI module&#39;s interface. You must have an active AI provider configured via the core AI module to process prompts and begin generating recipes.&lt;/p&gt;

&lt;h3 id=&quot;module-project--additional-requirements&quot;&gt;Additional Requirements&lt;/h3&gt;
&lt;p&gt;This module requires the following dependencies:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;AI (Artificial Intelligence) module ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;module-project--recommended-libraries&quot;&gt;Recommended modules/libraries&lt;/h3&gt;
&lt;p&gt;There are currently no additional recommended modules beyond the required AI ecosystem dependencies.&lt;/p&gt;

&lt;h3 id=&quot;module-project--similar-projects&quot;&gt;Similar projects&lt;/h3&gt;
&lt;p&gt;While standard Drupal site-building tools exist, this project uniquely leverages generative LLMs and the new Drupal Recipe API to architect configurations via natural language.&lt;/p&gt;

&lt;h3 id=&quot;module-project--support&quot;&gt;Supporting this Module&lt;/h3&gt;
&lt;p&gt;This project is currently being developed as part of &lt;strong&gt;Google Summer of Code 2026&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;module-project--community-documentation&quot;&gt;Community Documentation&lt;/h3&gt;
&lt;p&gt;Weekly progress, architectural breakdowns, and documentation for this GSoC 2026 project are being documented under the &lt;em&gt;Engineering in the Wild&lt;/em&gt; series.&lt;/p&gt;</code></pre>
<p>Further you can also add your logo here. It was also confusing for me to find out where the logo is. </p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780309200/drupal/rikmtxaeuoz4fwy1qz68.png" alt="Where to put Drupal Logo" /><figcaption>Where to put Drupal Logo</figcaption></figure>
<p>You are almost done!!! You can review and see the details what they are. And then finally you can save and your project is successfully created! You can view my module here:<a href="https://www.drupal.org/project/ai_recipe_generator">AI Recipe Generator</a></p>

<h1>Bonus: How to Add Maintainers</h1><p>Most probably, you will be working with someone else. And it will be necessary to add someone there. Head over to the Maintainers tab and select whom you want to add in the search box. You can select the necessary changes to be made and then press enter!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780309656/drupal/ydzkopdllpf4rjglsgyq.png" alt="For adding Maintainers" /><figcaption>For adding Maintainers</figcaption></figure>
<blockquote>
<p>Follow Along for my next journey</p>
</blockquote>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I got into Google Summer Of Code - The Recipe.]]></title>
            <description><![CDATA[A reflective guide on how to approach Google Summer of Code, presented as a recipe for success focusing on communication, consistency, and choosing the right project.]]></description>
            <link>https://blog.hrishikeshdalal.tech/drupal/drupal/6a12f76bfb799b375fde7a77</link>
            <guid isPermaLink="false">6a12f76bfb799b375fde7a77</guid>
            <pubDate>Sun, 24 May 2026 13:04:43 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>Since my project is called AI Recipe Generator (not the food one though). This blog is in a recipe format : )</blockquote>
<p>We all have been there where Google Summer of Code (GSoC) looks daunting to start, feeling its a tall mountain, difficult to climb. It looks like a challenge. Trust me I have been there. But in reality is just getting started and maintaining consistency. Let&#39;s now prep the kitchen!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780643149/drupal/clhycc3itdk7bmlgv7d8.png" alt="Put some image here" /><figcaption>It is worth it.</figcaption></figure>
<h1>The Spec Sheet</h1><ul>
<li><strong>Prep Time:</strong> 2.5 Months (On &amp; Off)</li>
<li><strong>Cook Time:</strong> 12 Weeks</li>
<li><strong>Servings:</strong> Mainly you!</li>
<li><strong>Difficulty:</strong> Easy (if you use the best ingredients!)</li>
</ul>

<h1>Ingredients Needed - 3C's</h1><p>1). <strong>Communication</strong>: The most important one. During the global GSoC welcome session, the organizers shared a powerful truth: <strong>our selection was 75% communication and only 25% coding</strong></p>
<p>2). <strong>Consistency</strong>: If you are regular, it is a point that people will see you will to collaborate. <em>(Although there is a work around this too)</em></p>
<p>3). <strong>Choosing</strong>: Make sure you choose the right repo &amp; right tech stack that fits you!</p>

<h1>Finding the right ingredients</h1><p>The most difficult part in GSoC is I believe finding out which repo to contribute to rather than the actual contribution. Choosing between your* time, tech stack and project complexity*, it a task. Even I spent quite a lot of time on deciding which one to actually to contribute too. </p>
<p>Had used this site: <a href="https://www.gsocorganizations.dev/">GSOC Organisation Site</a>. It <strong>helped me filter</strong> according to my tech stack (which was JS) and finally get to the repos. I had initially joined quite a few organisations as I had no clue how to go about it and shortlist it : )</p>

<p>I just got started, had not thought about the end output. What lingers when you start is where the easiest path forward is. Had tried RocketChat, and other repos where the tech stack was JS and available on github. </p>

<h2>Why did I stick with Drupal?</h2><p>COMMUNITY SUPPORT. There is a huge community there to help you if you get stuck. Somewhere down the line this made the difference which resulted in me never setting up another codebase of an organisation. Mind you, Drupal did have a steep learning curve, its not that easy to set up. But it worked in my favour once I set it up. Had posted a couple of doubts which truly helped me</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1779629489/drupal/lkanrhxuihezfsam69no.png" alt="Slack Msg" /><figcaption>My first message on slack</figcaption></figure>
<p><strong>You are going to get stuck! Work around it</strong></p>
<p>I believed in this and whenever I got stuck I used to put a message in the group and continue. Sure it felt uncertain but the community response put me right path.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1779629642/drupal/ixoe0grqakcezf9kf7zf.png" alt="Slack Msg" /><figcaption>Getting stuck on issues</figcaption></figure>
<h1>Important Learning</h1><p>Before we move on to the rest of the journey, let me tell you one of the most important learning. <em>If you approach GSoC as a competition you are going to be anxious and always in a mindset - The other person has done more issues, he/she is better than me I should do more.</em></p>
<blockquote>
<p>A small change in mindset: How can I contribute more? How can I assist more? This is what even the mentors and org admins look for</p>
</blockquote>

<h1>My RECIPE for GSoC</h1><h2>Step 1: Interact with Mentors & Community Early On</h2><p>Get in touch with your Mentor/s. They are going to guide you throughout your journey. And keep interacting with the community! In the GSoC welcome session with all contributors across all organisation, the organisers mentioned a point - if was 75% Communication, 25% Coding. That got you selected. This is the one of the most important part of your GSoC journey. </p>
<p>I had probably troubled my mentor a lot - asked a lot of questions. But he was also happy to guide me! </p>
<h2>Step 2: Contribution & Documentation</h2><p>In the organisation, contribute to the various issues. It will serve two purposes - making yourself familiar with the codebase &amp; a record to you name. This will come in handy when the mentor is deciding which candidate to choose. Your PR might not be accepted, or you might have some bug in your code (happened a lot with me), it&#39;s alright, put comments on issue / slack. If the issue isn&#39;t accepted atleast you got engagement &amp; interaction!</p>
<p><em>Document your progress.</em> This is a thing which worked for me &amp; a few people in Drupal had also done this. I had written a blog on Medium: <a href="https://medium.com/@1010hrishikesh/markdown-paradox-c5da304d3735">Markdown Paradox</a>. This was for an R&amp;D task in my project description for which I wrote a blog as well as documented in word which I had shared with my mentor. A few others also had made blogs / a website for the same. </p>
<p>Bonus: You can add this in your proposal!</p>

<pre><code class="language-plaintext">// What I put in my proposal

1 Personal Information
2 GSOC Information
3 Project Information
4 Abstract
5 Problem Statement
6 Technical Architecture &amp; Implementation
  6.1 Validating &amp; Caching
  6.2 Sample Implementation Code
7 Risk Analysis &amp; Edge Cases
8 Detailed Timeline
9 Testing
10 Project Impact
11 Deliverables
12 Research Tasks
13 About Me &amp; Community Experience</code></pre>
<h2>Step 3: Problem Statement & MVP</h2><p>You should have a thorough understanding about the Problem Statement that you are picking to solve! It is extremely important that you solve it. Discuss on it and share your thoughts &amp; ideas with your mentor. You could start with the R&amp;D tasks mentioned and move ahead!</p>
<p>**MVP will be the star of the show! ** It showcases your idea of the problem statement and your technical prowess. It gives you a lot of brownie points as you have understood the implementation and what it takes to make a good project. I had also made an MVP and given it two voice-overs as an added bonus.</p>
<p>My MVP: <a href="https://github.com/Hrishikesh-Dalal/drupal-ai-recipe-generator">https://github.com/Hrishikesh-Dalal/drupal-ai-recipe-generator</a></p>
<h2>Step 4: Proposal Writing</h2><p>This is really important if not the most important. This is your main criteria for judgement. What are your views on the project. Be sure to include architecture diagram, the drawbacks, migitations testing strategies etc.</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1779635425/drupal/otxtqh4x0ptqg00a4ngb.png" alt="System Diagram" /><figcaption>My System Diagram</figcaption></figure>
<h1>Conclusion</h1><p>Felt really amazing when i finally made it into GSoC. Happy to be there!</p>

<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1779636662/drupal/aky7bkhozspzzrum0d2n.png" alt="Chat between my Mentor" /><figcaption>A short chat between my mentor</figcaption></figure>
<figure><img src="https://res.cloudinary.com/dz5i84j9k/image/upload/v1780644656/drupal/l2ogzyzt6lnbsedtwu4c.png" alt="Drupal image" /></figure>]]></content:encoded>
        </item>
    </channel>
</rss>