<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://sylverstudios.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sylverstudios.dev/" rel="alternate" type="text/html" /><updated>2026-02-16T12:55:03+00:00</updated><id>https://sylverstudios.dev/feed.xml</id><title type="html">Sylver Studios</title><subtitle>An indie game dev group based in Boston. Sylver Studios' friends share this brand as a convenient place to co-publish fun toys.</subtitle><entry><title type="html">AI PDFing in 2025? Claude’s your guy</title><link href="https://sylverstudios.dev/blog/2025/07/28/ai-pdfing-in-2025.html" rel="alternate" type="text/html" title="AI PDFing in 2025? Claude’s your guy" /><published>2025-07-28T11:00:00+00:00</published><updated>2025-07-28T11:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/07/28/ai-pdfing-in-2025</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/07/28/ai-pdfing-in-2025.html"><![CDATA[<p>This is the post I wish I found the last 2 times I did this research. <a href="https://docs.anthropic.com/en/docs/build-with-claude/pdf-support#option-2%3A-base64-encoded-pdf-document"><img src="/assets/computer-no-pdf.webp" alt="ChatGPT generated image of a computer not understanding a PDF" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /></a><a href="/archive.html#ai">#ai</a> <a href="/archive.html#claude">#claude</a> <a href="/archive.html#llm">#llm</a> <a href="/archive.html#pdfs">#pdfs</a> <a href="/archive.html#tooling">#tooling</a> <a href="/archive.html#automation">#automation</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>Whether you’re improving your existing AI toolkit, or you’re proving the value of AI for your company, if you’ve got PDFs as part of the input data, I expect this will be valuable.</p>

<h2 id="tldr">tl;dr</h2>
<p>As of <strong>July 2025, the best way to analyze a PDF is via Anthropic’s API</strong>. Crazy easy, 1 API call, base64 PDF data and your prompt. The accuracy is fantastic, and it’s hilarious to compare the results of trying the same thing with openai. I had a hard time finding conclusions like this outside of forum posts, so hopefully this is useful to you for the next 6 months while it remains true ☺️.</p>

<p>If you need a reason to trust my opinion, or you’re curious, read on…</p>

<h2 id="how-i-got-here">How I got here</h2>

<p>More than a year ago I started a project to introduce our first AI driven feature and after a lot of testing, research into AI APIs, and research into our business problems, I decided on extracting structured data from PDFs. TL;DR from then, I needed to prove the ROI as clearly as possible and as the first person putting my neck out I was extremely focused on making it as small a slice as possible. At the time, you could put a PDF into chatgpt and it would be okay usually, and you could put a PDF into the API and it would be bad.</p>

<p class="example-image-container figure-caption"><img src="/assets/pdf-sample-image.webp" alt="Tiny PDF sample with some scanned and copied text" /></p>
<p><em>Example PDF with a chunk of a scanned form that’s been copied and is misaligned on the page. Reading Forms = $$</em></p>

<p>I ran experiments with every foundation model, extracting PDF content locally (<a href="https://pypi.org/project/pdfminer/">PDFMiner</a>, etc.) and pushing raw text as part of the prompt, I tried providers like AWS Textract, and <a href="https://unstract.com/">Unstract</a> (note: I actually thought their service was quite nice), and had a range of results. I was swimming in Google colab docs trying all of this. Ultimately, the strategy that yielded the best results, and fit my cost+time constraints was cumbersome, but effective. <strong>In November 2024, I would’ve recommended cutting the PDF by page, and converting each page to a jpeg, and then uploading those as files with the prompt.</strong> The multi-modal models at the time did a pretty excellent job with images and text, as well as getting the benefit of identifying images by page number in the responses. This is all to say, I spent a lot of time making that decision, and we did ship a production, profitable, compelling product that opened the door for more AI projects.</p>

<p>I’ve moved companies since then and we come across a somewhat similar usecase (funny enough it’s also driven by how confusing insurance can be) and I needed to refresh myself on that research. In November 2024, Anthropic had a beta feature to ease PDF analysis by basically doing the same PDF-&gt;Image process behind the scenes for you, but I didn’t have the bureaucratic temperament or time to get us approval for anthropic. Since then, it seems like that has gone GA and wow. Life. Is. Good.</p>

<p>What took me weeks for a PoC, and months to get it in production (not a fault of the tech), took me a week this time and roughly 1 day to be happy with the accuracy of the AI analysis.</p>

<h2 id="why-it-matters">Why It Matters</h2>
<ul>
  <li><strong>PDFs are everywhere</strong>: policies, contracts, intake forms.</li>
  <li><strong>PDFs are also bullshit</strong>: structured PDF documents, word docs as PDF, PDFs of scanned images of printed forms, it’s all “PDF”.</li>
  <li><strong>Sometimes a hammer is the right tool</strong>: We give-up on deterministically converting PDF structure to text reliably and just analyze the image.</li>
</ul>

<h2 id="vendor-specific-without-lock-in">Vendor specific without lock in</h2>
<p>As much as I’m an Anthropic fanboy, Anthropic did us a really nice favor by making this feature completely transparent. You don’t need to use a special API endpoint, you don’t need to upload the file to their magic assistant API before using it in your prompt, it’s just a single normal API call. If another provider offers a better solution, you won’t be boxed in. The <a href="https://docs.anthropic.com/en/docs/build-with-claude/pdf-support#option-2%3A-base64-encoded-pdf-document"><code class="language-plaintext highlighter-rouge">curl</code> PDF example</a> they give is really what you want. We use Elixir, and converting the curl command to a <code class="language-plaintext highlighter-rouge">Req</code> request is so easy, no provider client needed or AI framework, it’s just a web request.</p>

<h2 id="dont-take-it-from-me-heres-an-example">Don’t take it from me, here’s an example</h2>
<p>It’s easy to test and confirm that Anthropic is the best with PDFs, not only because the quality of life is so much better (see example code), but the quality of the results is there too. Credit to: <a href="https://docs.anthropic.com/en/docs/build-with-claude/pdf-support#option-2%3A-base64-encoded-pdf-document">Anthropic PDF Support</a> | You can download the sample image as a PDF <a href="/assets/pdf-sample.pdf">here</a>.</p>

<p>The PDF was ~2k input tokens, <code class="language-plaintext highlighter-rouge">claude-sonnet-4-20250514 </code> is $3.75/Mtokens, so we are looking at just under 1 cent(~$0.0075).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">base64</span> <span class="nt">-i</span> pdfSample.pdf | <span class="nb">tr</span> <span class="nt">-d</span> <span class="s1">'\n'</span> <span class="o">&gt;</span> pdf_base64.txt

<span class="c"># Create a JSON request file using the pdf_base64.txt content</span>
jq <span class="nt">-n</span> <span class="nt">--rawfile</span> PDF_BASE64 pdf_base64.txt <span class="s1">'{
    "model": "claude-sonnet-4-20250514",
    "max_tokens": 1024,
    "messages": [{
        "role": "user",
        "content": [{
            "type": "document",
            "source": {
                "type": "base64",
                "media_type": "application/pdf",
                "data": $PDF_BASE64
            }
        },
        {
            "type": "text",
            "text": "Give me a 1 sentence answer or less, what is a revenue value from this document?"
        }]
    }]
}'</span> <span class="o">&gt;</span> request.json

<span class="c"># Send the API request using the JSON file</span>
curl https://api.anthropic.com/v1/messages <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"content-type: application/json"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"x-api-key: </span><span class="nv">$ANTHROPIC_API_KEY</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"anthropic-version: 2023-06-01"</span> <span class="se">\</span>
  <span class="nt">-d</span> @request.json
</code></pre></div></div>

<h2 id="bonus">Bonus</h2>
<p>Try converting a weird scanned form today and see if Claude can beat your current stack. It’s genuinely as easy as it looks.</p>

<p>To my AI first thinkers, the Anthropic docs even have a button to get the page as markdown. I usually give the URL to claude code and let it scrape however it wants, but if you’re building a local doc center for stuff like this, you can just give the docs to your AI of choice and ask for an implementation in your language and code style.</p>

<p class="example-image-container figure-caption"><img src="/assets/anthropic-docs.webp" alt="Anthropic PDF Doc with Copy Button" /></p>
<p><em>Image of the Anthropic docs header, including a “copy” button with dropdown for format of copy for LLM convenience</em></p>

<div class="substack-button" style="text-align: center; margin: 2em 0;">
  <a href="https://sylverstudios.substack.com/?r=1u7tgy&amp;utm_campaign=pub-share-checklist" class="button" style="display: inline-block; padding: 0.8em 1.5em; background-color: #FF6719; color: white; text-decoration: none; border-radius: 4px; font-weight: 600; transition: background-color 0.2s;">Visit Substack to Subscribe</a>
</div>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="ai" /><category term="claude" /><category term="llm" /><category term="pdfs" /><category term="tooling" /><category term="automation" /><summary type="html"><![CDATA[This is the post I wish I found the last 2 times I did this research. #ai #claude #llm #pdfs #tooling #automation]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/computer-no-pdf.webp" /><media:content medium="image" url="https://sylverstudios.dev/assets/computer-no-pdf.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How Claude Code and MCP Helped Me Build a Custom Phoenix 404 Page</title><link href="https://sylverstudios.dev/blog/2025/07/08/how-claude-code-and-mcp-helped-me.html" rel="alternate" type="text/html" title="How Claude Code and MCP Helped Me Build a Custom Phoenix 404 Page" /><published>2025-07-08T05:00:00+00:00</published><updated>2025-07-08T05:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/07/08/how-claude-code-and-mcp-helped-me</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/07/08/how-claude-code-and-mcp-helped-me.html"><![CDATA[<p>Agents have changed how I code. The slowest part of the dev loop is now me! <a href="https://pptr.dev/"><img src="/assets/puppeteer-logo.png" alt="puppeteer logo" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /></a><a href="/archive.html#ai">#ai</a> <a href="/archive.html#claude">#claude</a> <a href="/archive.html#claude-code">#claude-code</a> <a href="/archive.html#mcp">#mcp</a> <a href="/archive.html#phoenix">#phoenix</a> <a href="/archive.html#puppeteer">#puppeteer</a> <a href="/archive.html#automation">#automation</a> <a href="/archive.html#tutorial">#tutorial</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>In my <a href="/blog/2025/04/30/ai-for-refinement.html">Cursor post</a>, I demonstrated how I utilized a verification script to assist Claude Code in verifying its work. However, until recently, UI changes still required me to manually run the server and take screenshots, which I would then share with Cursor along with feedback. Note that this was still a significant improvement, as it could now perform the design UI iteration with screenshots.</p>

<p>I’ve been <a href="https://www.anthropic.com/news/model-context-protocol">MCP</a><sup>1</sup> curious, but I had not, until now, seen a valuable practical application. Enter <a href="https://www.npmjs.com/package/@modelcontextprotocol/server-puppeteer">Puppeteer</a>. With a Puppeteer MCP, Claude Code can drive the browser. The agent can navigate, interact, and take screenshots without requiring my intervention. What’s significant here is that there’s now one fewer step where I am the bottleneck. <strong>Claude can self-review and provide a visual critique, which will then improve before I have to evaluate it manually.</strong></p>

<p class="example-image-container figure-caption"><img src="/assets/mcp-fsm.webp" alt="MCP FSM Diagram" /></p>
<p><em>Top: My previous workflow, Claude can self-iterate via automated tests. Bottom: Current workflow, Claude can self-iterate via tests, and UI QA via Puppeteer. Saving me a step in the process</em></p>

<p>Part of my inspiration for this was an article about <a href="https://crawshaw.io/blog/programming-with-agents">Programming with Agents</a>, because it illustrated the importance of enabling feedback and even how a simple <code class="language-plaintext highlighter-rouge">bash</code> tool is enough to unlock a massive boost in LLM capability. Now to the meat.</p>

<h1 id="lets-build-a-custom-phoenix-404">Let’s build a custom Phoenix 404</h1>

<p>I love the playful 404 pages, like <a href="https://github.com/fake-address">GitHub’s 404</a>. I’ve always wanted to add one to my apps, but the combo of limited artistic ability, time, and it never feeling like a priority prevented me from making one.</p>

<p>Exploring MCP finally gave me the right opportunity. You can try this out in less than an hour. Setting up Puppeteer via NPX was a mix of super simple and, as a result, extremely confusing when it wasn’t working. It’s just one configuration<sup>2</sup>, and it <em>should</em> run. This project is essentially design-driven; it’s a public route with no interactivity, providing a perfect example to focus on tooling.</p>

<p class="example-image-container figure-caption"><img src="/assets/404-example.webp" alt="404 Example Design" /></p>
<p><em>Left: Mock made by ChatGPT | Center: First attempt from Claude | Right: Finished product</em></p>

<h1 id="start-with-a-target-ux">Start with a target UX</h1>

<p>I brainstormed ideas with ChatGPT and generated an image I liked. You can see it above; ideally, Claude Code can do this for me one day as well. (This took a couple of turns to refine my idea and tweak the design language and art) I gave it info on our business, the tone I was looking for, the thematic style, how I wanted users to feel (<em>calm</em>), and what they might already be feeling (<em>stressed, frustrated, exasperated, desperate</em>).</p>

<h1 id="implement-with-claude--puppeteer">Implement with Claude &amp; Puppeteer</h1>

<p>I started a Claude Code session with my design file in my repo and directions that were not clear enough, eventually landing on quite structured instructions and a link to the <a href="https://hexdocs.pm/phoenix/custom_error_pages.html">Phoenix custom error docs</a>. Claude nailed the normal coding challenges, getting a custom error page working in its first prompt and making a nice automated test. This is a huge improvement from the past because it can so effectively read the doc link I provided. It even taught me how they work (skipping the “layout” work that pages use and containing the body and layout in the HTML).</p>

<p>I eventually got good results and an effective self-review by asking for these steps to be followed:</p>

<blockquote>
  <p>use puppeteer to visit a specific route <code class="language-plaintext highlighter-rouge">/nonsense</code>, take a screenshot, compare that screenshot to the design file, make notes of each difference between the images, address the top 3 differences, repeat until the browser looks reasonably close to the design and would provide a nice user experience.</p>
</blockquote>

<p>This took a couple of turns as I realized what I was looking for and gave some specific feedback for responsive design, which did a great job with different resolutions.</p>

<h1 id="i-got-a-404-i-was-happy-with-and-i-didnt-code">I got a 404 I was happy with, and I didn’t code.</h1>

<p>The agent ran the loop: <code class="language-plaintext highlighter-rouge">code -&gt; browser -&gt; screenshot -&gt; compare -&gt; repeat</code>. I only nudged it when needed. Phoenix made it easy to wire up, especially by including the docs URL in my instructions. The main point I want to convey is that we’re no longer simply generating code with the agent. Instead, the agent now actively participates in development activities. Generating code is just one of its functions, rather than a peripheral tool I use for development. Each piece like this, where I can get out of the loop, brings us closer to the agent-based future.</p>

<p>Bonus: I read a similar article on <a href="https://lucumr.pocoo.org/2025/06/12/agentic-coding/">Agentic Coding</a>, but with a huge emphasis on the speed of your iteration tools. If you’re already getting success from things like I mentioned above, this article will take it a step further. This assumes that your AI tooling needs to be able to evaluate its work and revise it; the next goal is then to make that feedback as fast as possible.</p>

<p>If you’ve been working on your AI workflow, I’d love to hear about it and share ideas. I’m still not entirely convinced that MCPs hold significant value for me right now, but I’m starting to see their potential.</p>

<hr />

<p><strong>Footnotes:</strong></p>

<p><sup>1</sup> MCP is a standard defined by Anthropic to enable “plug and play” tool sets for things like Computer use, or the Sentry API, etc. Ideally, you can share code that’s like an SDK for an Agent to use.</p>

<p><sup>2</sup></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"puppeteer"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"-y"</span><span class="p">,</span><span class="w"> </span><span class="s2">"puppeteer-mcp-server"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"env"</span><span class="p">:</span><span class="w"> </span><span class="p">{}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="substack-button" style="text-align: center; margin: 2em 0;">
  <a href="https://sylverstudios.substack.com/?r=1u7tgy&amp;utm_campaign=pub-share-checklist" class="button" style="display: inline-block; padding: 0.8em 1.5em; background-color: #FF6719; color: white; text-decoration: none; border-radius: 4px; font-weight: 600; transition: background-color 0.2s;">Visit Substack to Subscribe</a>
</div>

<p>Praise the editor: <a href="https://github.com/samgqroberts">Sam Roberts</a></p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="ai" /><category term="claude" /><category term="claude-code" /><category term="mcp" /><category term="phoenix" /><category term="puppeteer" /><category term="automation" /><category term="tutorial" /><summary type="html"><![CDATA[Agents have changed how I code. The slowest part of the dev loop is now me! #ai #claude #claude-code #mcp #phoenix #puppeteer #automation #tutorial]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/puppeteer-logo.png" /><media:content medium="image" url="https://sylverstudios.dev/assets/puppeteer-logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Iterate and refine with the Cursor Agent</title><link href="https://sylverstudios.dev/blog/2025/04/30/ai-for-refinement.html" rel="alternate" type="text/html" title="Iterate and refine with the Cursor Agent" /><published>2025-04-30T12:00:00+00:00</published><updated>2025-04-30T12:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/04/30/ai-for-refinement</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/04/30/ai-for-refinement.html"><![CDATA[<p>In this post, I’ll show you how I use <a href="https://docs.cursor.com/chat/agent">Cursor Agent Mode</a> to refine UI and polish tests — fast, visually, and interactively.<a href="https://www.cursor.com/"><img src="/assets/cursor-logo.jpeg" alt="cursor logo" class="long-excerpt-image" /></a><a href="/archive.html#ai">#ai</a> <a href="/archive.html#cursor">#cursor</a> <a href="/archive.html#ui">#ui</a> <a href="/archive.html#refactoring">#refactoring</a> <!-- Ends the excerpt text, it includes the image --></p>

<p><em>It’s a natural follow-up to our scaffolding with Claude Code, and a powerful tool for targeted improvements. Check out <a href="/blog/2025/04/23/ai-as-architect.html">AI Architecture and Scaffolding with Claude Code</a> in the <a href="/blog/2025/04/16/ai-developer-mindset.html">AI development Mindset: Coach</a> series. We continue from our scaffolding the project with help from <a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview">Claude Code</a>.</em></p>

<h1 id="ui-edits-with-screenshots">UI Edits with Screenshots</h1>

<p>Cursor’s multi-modal capabilities are extremely undervalued. Screenshots help your coworkers understand what you’re aiming for, and Cursor is no exception! Here’s my typical workflow:</p>

<ol>
  <li>Take a screenshot of the current app</li>
  <li>Take a screenshot of the design</li>
  <li>Prompt with specific changes:
    <blockquote>
      <p>“Make the app look more like the design. Flatten the form header, move the input to the left, and use the brand color for the background.”</p>
    </blockquote>
  </li>
</ol>

<p>I iterate with more screenshots and feedback:</p>

<p class="example-image-container"><img src="/assets/cursor-image-agent-example.png" alt="Cursor Agent Example with this blog posts styling issues" /></p>

<h1 id="refactoring-and-test-updates">Refactoring and Test Updates</h1>

<p>Cursor’s also great for test updates and refactoring. I often ask:</p>
<blockquote>
  <p>“Make @this_test.exs work like @my_gold_standard_test.exs. Share test setup. Reduce file length, keep coverage.”</p>
</blockquote>

<p>Just like UI edits, focused prompts and examples help.
When context matters (e.g., not using <code class="language-plaintext highlighter-rouge">Mox</code> in Elixir tests), <a href="https://docs.cursor.com/context/rules-for-ai">Cursor Rules</a> let you inject project-specific instructions. If you want a deep dive, this article (<a href="https://ghuntley.com/stdlib/">You are using Cursor AI incorrectly…</a>) and <a href="(https://ihorkatkov.github.io/blog/2025/from-skeptic-to-ai-believer/)">this case study</a> were really impactful to me.</p>

<h1 id="the-review-process">The Review Process</h1>

<p>As with Claude Code, I review each diff like a PR. Feedback goes directly into the prompt. I stage the changes I like. I commit once a full thought is resolved. Cursor’s addition is the ability to reference Rules, and Notebooks where you can store extra context and even directly reference functions by starting to type <code class="language-plaintext highlighter-rouge">@function_name</code> and it will auto suggest as you type.</p>

<h1 id="knowing-when-to-stop">Knowing When to Stop</h1>

<p>Eventually, you’ll hit the wall: if the change is small, tedious, or personal-style-based, stop asking AI and do it yourself. There are diminishing returns as code changes get smaller. More specific changes need more description and start changing fewer and fewer lines, you will cross a point where you need to type more than the the changes that you know how to code.</p>

<blockquote class="callout">
  <p>Stop when your explanations are getting longer and changes are getting smaller.</p>
</blockquote>

<h1 id="heuristics-for-success">Heuristics for Success</h1>

<ol>
  <li><strong>Use Visual Context</strong>
    <ul>
      <li>Screenshots are worth a thousand words</li>
      <li>Show before and after states</li>
      <li>Reference existing patterns</li>
    </ul>
  </li>
  <li><strong>Keep Prompts Focused</strong>
    <ul>
      <li>One change at a time (I cheat with numbered lists)</li>
      <li>Be specific about what to change</li>
      <li>Reference existing code</li>
    </ul>
  </li>
  <li><strong>Know Your Limits</strong>
    <ul>
      <li>Stop when prompts get too long</li>
      <li>Switch to manual changes for small tweaks</li>
      <li>Use AI for what it’s good at (css)</li>
    </ul>
  </li>
</ol>

<h1 id="the-bottom-line">The Bottom Line</h1>

<p>Cursor shines when you need to turn good-enough code into nearly-production-ready polish—without writing every line yourself. It’s uniquely powerful for UI work with images. and test refinement for UI, where visual context and pattern matching are key. If you liked this series, send word! I’m excited to share more if it’s valuable to folks out there (or future me as well).</p>

<div class="substack-button" style="text-align: center; margin: 2em 0;">
  <a href="https://sylverstudios.substack.com/?r=1u7tgy&amp;utm_campaign=pub-share-checklist" class="button" style="display: inline-block; padding: 0.8em 1.5em; background-color: #FF6719; color: white; text-decoration: none; border-radius: 4px; font-weight: 600; transition: background-color 0.2s;">Visit Substack to Subscribe</a>
</div>

<p>Praise the editor: <a href="https://github.com/samgqroberts">Sam Roberts</a></p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="ai" /><category term="cursor" /><category term="ui" /><category term="refactoring" /><summary type="html"><![CDATA[In this post, I’ll show you how I use Cursor Agent Mode to refine UI and polish tests — fast, visually, and interactively.#ai #cursor #ui #refactoring]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/cursor-logo.jpeg" /><media:content medium="image" url="https://sylverstudios.dev/assets/cursor-logo.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI Architecture and Scaffolding with Claude Code</title><link href="https://sylverstudios.dev/blog/2025/04/23/ai-as-architect.html" rel="alternate" type="text/html" title="AI Architecture and Scaffolding with Claude Code" /><published>2025-04-23T05:00:00+00:00</published><updated>2025-04-23T05:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/04/23/ai-as-architect</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/04/23/ai-as-architect.html"><![CDATA[<p>I walk through using <a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview">Claude Code</a> to plan and start projects.<a href="https://www.anthropic.com/"><img src="/assets/anthropic-logo.jpeg" alt="anthropic logo" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /></a><a href="/archive.html#ai">#ai</a> <a href="/archive.html#claude">#claude</a> <a href="/archive.html#claude-code">#claude-code</a> <a href="/archive.html#architecture">#architecture</a> <a href="/archive.html#tdd">#tdd</a> <a href="/archive.html#tutorial">#tutorial</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>This is a follow-up to better explain my approach to using AI coding tools in <a href="/blog/2025/04/16/ai-developer-mindset.html">AI development Mindset: Coach</a>. This is how I start projects that are big enough to warrant atleast a 15 minute planning session before starting. I have scripts that I use to handle the rote work described here, but it’s really important to talk through (and experience) it first hand to help you build up your own tools. I found this article by Manuel Kießling, <a href="https://manuel.kiessling.net/2025/03/31/how-seasoned-developers-can-achieve-great-results-with-ai-coding-agents/">Senior Developer Skills in the AI Age: Leveraging Experience for Better Results</a> really similar to how I see a lot of this work and wanted to show what it looks like for my work.</p>

<h1 id="the-prompt-template">The Prompt Template</h1>

<p>Every session starts with the same context structure:</p>

<ol>
  <li><strong>Business Context</strong>: What does our business do?</li>
  <li><strong>App Purpose</strong>: What does our app do?</li>
  <li><strong>New Problem</strong>: What are we trying to solve and for who?</li>
  <li><strong>Desired Outcome</strong>: What does success look like?</li>
</ol>

<blockquote class="callout">
  <p>Prompt by explaining the business context first. Then define outputs and guardrails.</p>
</blockquote>

<h1 id="the-planmd-structure">The PLAN.md Structure</h1>

<p>I have Claude turn this context into a PLAN.md file that includes:</p>

<ol>
  <li>A restatement of the context, problem statement, and outcome</li>
  <li>A milestone breakdown, where each milestone includes:
    <ul>
      <li>A testable unit of work</li>
      <li>An automated test to prove it’s done</li>
    </ul>
  </li>
</ol>

<p>If I already have architectural ideas, I include them. If not, I’ll ask for three architecture options—always including one wild-card idea to stretch creativity—before moving to the milestone plan. I use <a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/tutorials#create-custom-slash-commands">Claude Code Custom Slash Commands</a> for this, where I have a template plan file including the business context and a real sample project that we did and it was the fully completed PLAN doc that I ended with. You can checkout an <a href="https://gist.github.com/shamshirz/eb1dac86bc7238f228ed58d1fac5fba2#file-plan-md">example of mine in this gist</a>.</p>

<h1 id="the-development-loop">The Development Loop</h1>

<p>Once the plan looks good, I give it the go-ahead.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Start on milestone 1. Use TDD. Update the PLAN.md with progress. And always run the verify.sh script after changes to be sure everything is working.
</code></pre></div></div>
<p>It’s important to note that the biggest boost we are unlocking allows Claude Code to make changes, fix them, and continue on it’s own until the whole plan is complete, running completely autonomously with no additional input from me for ~5-8 minutes. The plan needs to be clear and the <a href="https://gist.github.com/shamshirz/eb1dac86bc7238f228ed58d1fac5fba2#file-verify-sh">feedback script - verify.sh</a>, just all of our deterministic testing/linting/even dev server, is so fast and easy that CC can <strong>self-iterate</strong> accurately and with clear next steps.. This is the Critical Victory that I’ve seen from CC that Cursor can’t match yet.</p>

<p>When the first diff is ready, I treat it like a PR. I review each file and leave feedback in the prompt as a numbered list. I expect working, test-passing code after 1–2 rounds and hitting that or not is a reflection on my ability to prompt.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 1. File A duplicates File B—centralize that logic.
2. Rename ActionItem to NextSteps, and update tests and callers.
3. Use an adapter pattern around the HTTP client—see File D for reference.
4. Refactor UI code to match patterns from File E.
</code></pre></div></div>

<h1 id="managing-changes">Managing Changes</h1>

<p>I stage diffs as I go—if something’s mostly right, I keep it but haven’t committed yet. That way, I can trace each “chain of thought” to 1 commit, and incremental improvements of that commit are managed through staged diffs. Typically, I get 1–2 commits from this stage. Sometimes I don’t like the changes it makes, which is why I rely on staging to avoid losing anything useful. I drop the incoming changes, and provide the same review with the added context that it was wrong and I removed the changes. You can use anything to solve this, I just find the diff view of staged changes versus unstaged to be the easiest to compare.</p>

<h1 id="when-to-use-claude-code">When to Use Claude Code</h1>

<p>Claude Code is much slower and more expensive than the Cursor Agent. Even the simplest task will take minutes, not seconds. I often complete a project that’s reasonably big ~2-3 PRs and spend $5 to $10. Personally, if this speeds me up 10%, it’s easily worth the cost, and I’ll multi-task while claude works (🙊). Only accept this overhead while you’re making broad or complex changes. Stop when the structure and relationship between files is where you want it, even if you have specific code changes you still want to implement.</p>

<h1 id="time-expectations">Time Expectations</h1>

<p>For a typical feature, I budget:</p>
<ul>
  <li>15m of context + planning</li>
  <li>30–60m of architecture + initial build</li>
  <li>30m of refinement (next post)</li>
  <li>15m of self-review + polish</li>
</ul>

<p>The reward isn’t just speed—it’s focus. I get to spend more time making thoughtful decisions and typing boilerplate less. Claude Code takes minutes, not seconds, so I use that to explore a separate change in parallel, read docs, or continue designing the next change. The fundamental change is that I am able to keep my perspective on the level of architecture and code organization more, and implementation less. Next up, I’ll walk through what I do after Claude Code, where I really like the Cursor Agent.</p>

<div class="substack-button" style="text-align: center; margin: 2em 0;">
  <a href="https://sylverstudios.substack.com/?r=1u7tgy&amp;utm_campaign=pub-share-checklist" class="button" style="display: inline-block; padding: 0.8em 1.5em; background-color: #FF6719; color: white; text-decoration: none; border-radius: 4px; font-weight: 600; transition: background-color 0.2s;">Visit Substack to Subscribe</a>
</div>

<p>Praise the editor: <a href="https://github.com/samgqroberts">Sam Roberts</a></p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="ai" /><category term="claude" /><category term="claude-code" /><category term="architecture" /><category term="tdd" /><category term="tutorial" /><summary type="html"><![CDATA[I walk through using Claude Code to plan and start projects.#ai #claude #claude-code #architecture #tdd #tutorial]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/anthropic-logo.jpeg" /><media:content medium="image" url="https://sylverstudios.dev/assets/anthropic-logo.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI development Mindset: Coach</title><link href="https://sylverstudios.dev/blog/2025/04/16/ai-developer-mindset.html" rel="alternate" type="text/html" title="AI development Mindset: Coach" /><published>2025-04-16T12:00:00+00:00</published><updated>2025-04-16T12:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/04/16/ai-developer-mindset</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/04/16/ai-developer-mindset.html"><![CDATA[<p>How I’m thinking about AI development from a new perspective<img src="/assets/robot-clipboards.png" alt="robots holding clipboards: ai image" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /><a href="/archive.html#ai">#ai</a> <a href="/archive.html#mindset">#mindset</a> <a href="/archive.html#how-to">#how-to</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>Over the past year, I have transitioned from self-exploration of AI to building an AI adoption plan for a team of 30 engineers and now back to solo daily use of AI. My opinion on what AI offers and how I interact with it has changed dramatically. A change in perspective about collaborating with AI enables the most significant productivity gains.</p>

<h1 id="treat-ai-like-a-new-engineer">Treat AI Like a New Engineer</h1>
<p>My most significant improvement in my dev workflow with AI has come from a mindset shift: My AI is a superhuman engineer with zero awareness. A fast, tireless contractor who knows everything except my business, users, and codebase. They just showed up, and I need to manage them.</p>

<blockquote class="callout">
  <p>If you wouldn’t expect a brand-new dev to succeed without context, you shouldn’t expect it from your AI.</p>
</blockquote>

<p>You don’t have to use AI, just like you don’t <em>have</em> to use a new engineer on every project. But if you do, your job is to provide an environment where it can thrive—and to recognize when a task isn’t a fit. AI shines with a clear, broad context, a strong feedback loop, and a well-defined goal.</p>

<h4 id="-aside-be-real-about-what-ai-is">📝 Aside: Be real about what AI is</h4>

<p class="note">AI is not magic. That said, it’s not a person, and you need to handle some limitations. It’s extremely fast, absurdly knowledgeable, has no business awareness, memory limited to the length of your session, and only “sees” your code base in slivers at a time. That’s not a knock—it’s just the job description. Accept its strengths and weaknesses, and it can do fantastic work. In general, let your guiding principle be “treat AI like a new engineer,” and be aware of tactics to work within its limitations.</p>

<h1 id="tactical-advice-for-working-with-ai">Tactical Advice for Working with AI</h1>

<ol>
  <li><strong>Provide Clear Context</strong>
    <ul>
      <li>What does your business do, and why does this problem matter?</li>
      <li>Define clear goals and outcomes</li>
      <li>Set explicit boundaries and constraints</li>
    </ul>
  </li>
  <li><strong>Review Like a PR</strong>
    <ul>
      <li>Give specific, actionable feedback</li>
      <li>Iterate on changes systematically</li>
      <li>You’re a coach, not a mentor, tell it what to do</li>
    </ul>
  </li>
  <li><strong>Know When Not to Use AI</strong>
    <ul>
      <li>Simple tasks that you already know and can complete as fast as you can type</li>
      <li>Minor changes very specific to your code base</li>
      <li>When the change depends on specific data (bug with a specific user in your logs)</li>
    </ul>
  </li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># bad</span>
<span class="o">&gt;</span> <span class="s2">"make this controller able to accept a PUT request"</span>

<span class="c"># good - and ideally mostly automatically added</span>
<span class="o">&gt;</span> <span class="s2">"Our Customer Service business has an opportunity to accept incoming updates from MegaCorp. 
If we can accept their requests we can save users hours. Their documentation is at mega-corp.net/docs (Tell me if you can't read them)
they will be sending us the Money object via a PUT request, and @nice-example.code is a file that does something similar, implement it like that.
Be sure to use TDD practices and run the @verify script after everychange before you tell me it's done.
"</span>
</code></pre></div></div>

<p>If that example seems long, you’re right. This makes a difference, and it’s why I script the bulk of that context. AND there comes a point where you will absolutely have diminishing returns because thorough input is so valuable; the result needs to be high value to be worth it.</p>

<h1 id="the-bottom-line">The Bottom Line</h1>

<p>AI shines with a clear, broad context, a strong feedback loop, and a well-defined goal. I talked about this as it related to Elixir in my last post <a href="/blog/2025/03/25/elixir-ai.html">Elixir’s Advantage in the Era of AI</a>. I’ve seen some great results so far, and the mindset is what leads to creating the best prompts and the proper context. I will explore using Claude Code for scaffolding and Cursor for polish soon. Stay tuned!</p>

<p>I’m also experimenting with using Substack to provide a newsletter. Let me know if you have any thoughts, and subscribe to get these pushed directly to your HUD contact lenses.</p>

<h2 id="aaron-on-the-sylverstudios-substack"><a href="https://sylverstudios.substack.com/?r=1u7tgy&amp;utm_campaign=pub-share-checklist">Aaron on the Sylverstudios Substack</a></h2>

<p>Praise the editors: <a href="https://github.com/samgqroberts">Sam Roberts</a> &amp; Dan Janowski</p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="ai" /><category term="mindset" /><category term="how-to" /><summary type="html"><![CDATA[How I’m thinking about AI development from a new perspective#ai #mindset #how-to]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/robot-clipboards.png" /><media:content medium="image" url="https://sylverstudios.dev/assets/robot-clipboards.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Elixir’s Advantage in the Era of AI</title><link href="https://sylverstudios.dev/blog/2025/03/25/elixir-ai.html" rel="alternate" type="text/html" title="Elixir’s Advantage in the Era of AI" /><published>2025-03-25T12:00:00+00:00</published><updated>2025-03-25T12:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/03/25/elixir-ai</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/03/25/elixir-ai.html"><![CDATA[<p>My experience with AI-assisted coding <a href="https://elixir-lang.org/"><img src="/assets/elixir-logo.jpeg" alt="elixir logo" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /></a><a href="/archive.html#elixir">#elixir</a> <a href="/archive.html#phoenix">#phoenix</a> <a href="/archive.html#liveview">#liveview</a> <a href="/archive.html#ai">#ai</a> <a href="/archive.html#cursor">#cursor</a> <a href="/archive.html#claude-code">#claude-code</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>I’ve been deep in AI dev tooling for about 10 months, and I’m now fully bought in on the future of AI-assisted development. Tools like <a href="https://www.cursor.com/">Cursor</a> and <a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview">Claude Code 🚀</a> are no longer just autocomplete—they can self-iterate. They’ll run tests, interpret failures, revise code without my intervention, and do it until everything works.</p>

<p>And the more structured feedback you feed these tools—errors, types, docs, tests—the better they perform. We want to make each iteration as fast and accurate as possible. That’s where Elixir quietly shines.</p>

<p>Despite being a small slice of training data, Elixir is uniquely well-positioned to thrive in this new model. <strong>It has three traits that stack up beautifully for AI pair programming: First-Class Documentation, Functional + Compiled Design, and a Deeply Integrated Testing Model (thanks, BEAM).</strong></p>

<h2 id="1-first-class-documentation-context-built-into-the-code">1. First-Class Documentation: Context Built into the Code</h2>

<p>Elixir treats documentation as a first-class citizen. Every function and module has structured, built-in documentation next to the code. It’s not just scattered comments—it’s compiled, queryable, and often includes real usage examples through <code class="language-plaintext highlighter-rouge">doctests</code>. That can be run and updated by our AI!</p>

<p>This is a goldmine for LLMs. Instead of guessing how a function works, AI tools can read structured descriptions, see example inputs/outputs, and even run the embedded tests. This means fewer hallucinations, better API usage, and more accurate code. Elixir isn’t alone in great doc support, but it compounds the benefits with these other features.</p>

<h2 id="2-functional--compiled-predictable-debuggable-and-ai-friendly">2. Functional &amp; Compiled: Predictable, Debuggable, and AI-Friendly</h2>

<p>Grouping these may be cheating, but any functional, compiled language has an edge for AI. Functional code is more straightforward for people to reason about because you should only need the context of the function you are in. No side effects on variables outside the scope, no class variables to chase down, and the AI’s context window thanks you. Elixir is not alone here, either, but the combination with #1 narrows the field significantly</p>

<p>Elixir is compiled, which means AI tools (and developers) get immediate, structured feedback before your code runs, pointing out exactly what’s wrong. As a minor bonus, Elixir’s <a href="&quot;https://hexdocs.pm/elixir/gradual-set-theoretic-types.html&quot;">Gradual Set-Theoretic Types</a> are closing the gap to static compiled and strongly typed languages. Instead of failing silently at runtime, compilation errors tell you exactly where the problem is—which is as helpful for an AI refactoring code as it is for a human. This keeps the iteration loop for the AI tight and efficient because it will be more likely to make the correct change after an error than to receive a message like <code class="language-plaintext highlighter-rouge">null is not an object.</code></p>

<h2 id="3-elixir-best-tests-in-the-west-">3. Elixir, Best Tests in the West 👈🤠👉</h2>

<p>One of Elixir’s biggest strengths is how deeply its testing framework integrates with real application behavior. This has a caveat, if you are using a frontend framework for an SPA, you lose some of this benefit. Because Elixir is Erlang running on the <a href="https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)">BEAM (Bogdan/Björn’s Erlang Abstract Machine)</a>, we can run your entire application as a collection of processes and interact with it from “outside.” The side bonus of being able to run most tests concurrently is a spooky fast feedback loop. I’m using <a href="&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html&quot;">Phoenix Liveview</a> and avoiding an SPA for now, which means I can also get UI tests that run the whole application, and trigger UI events on button clicks or navigation links. I’ve never been a fan of full integration tests—too much setup and maintenance, and they still miss things. But now, the cost has gone down with AI writing and maintaining tests while the value has gone up because the same AI can benefit from the feedback that used to require manual aid.</p>

<p>That means AI tools can:</p>

<ul>
  <li>Interact with the full app—API, UI, and DB—in native tests.</li>
  <li>Get structured failures and iterate without manual debugging.</li>
  <li>Build and maintain integration and UI tests.</li>
</ul>

<h2 id="ai-has-changed-how-i-see-elixir">AI has changed how I see Elixir</h2>

<p>My experience using LLMs with Elixir—especially Cursor and Claude Code—has been incredible. I will give lots of context on the situation, ask to start by making a <code class="language-plaintext highlighter-rouge">TODO.md</code> implementation plan that includes the tests in TDD style, and let the agent build features, run the suite, and iterate until it’s done. Elixir isn’t the only language with these capabilities, but the combination of clear docs, fast compiler feedback, and integrated testing makes it uniquely effective. It has been a delight to work with it and build enough context for it to navigate my code like a seasoned young engineer.</p>

<p>That’s not a future I’m waiting for—it’s already here.</p>

<p>I’m betting on the future of AI-driven development. JS and Python devs may have a head start, but I don’t think they ride the best horse. Elixir has the right DNA—and our way forward is to create, share, and lead with great code. That’s something this community excels at. Writing this blog (and sharing more code) is part of that effort.</p>

<p>We have a moment. Let’s make sure Elixir rises with the AI tide.</p>

<p class="note">To share the credit, if you’re working in a statically typed language as well as function? (<em>cough: Elm</em> 😍) Then your compiler is a gold mine for an AI assistant to work with, line numbers, exact type references, even calling out specific function signatures and variables that have the issue :chef-kiss:.</p>

<p>TY to <a href="https://github.com/rjdellecese">RJ Dellecese</a> for editing.</p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="elixir" /><category term="phoenix" /><category term="liveview" /><category term="ai" /><category term="cursor" /><category term="claude-code" /><summary type="html"><![CDATA[My experience with AI-assisted coding #elixir #phoenix #liveview #ai #cursor #claude-code]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/elixir-logo.jpeg" /><media:content medium="image" url="https://sylverstudios.dev/assets/elixir-logo.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Elixir Testing Guide: Commanded and Event Sourcing</title><link href="https://sylverstudios.dev/blog/2025/03/02/testing-commanded.html" rel="alternate" type="text/html" title="Elixir Testing Guide: Commanded and Event Sourcing" /><published>2025-03-02T15:39:11+00:00</published><updated>2025-03-02T15:39:11+00:00</updated><id>https://sylverstudios.dev/blog/2025/03/02/testing-commanded</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/03/02/testing-commanded.html"><![CDATA[<p>Wrapping my head around testing Event Sourced systems <a href="https://github.com/commanded/commanded"><img src="/assets/commanded-logo.png" alt="commanded logo" class="excerpt-image" style="border-radius: 8px; width: 80px; height: auto;" /></a><a href="/archive.html#elixir">#elixir</a> <a href="/archive.html#phoenix">#phoenix</a> <a href="/archive.html#event-sourcing">#event-sourcing</a> <a href="/archive.html#commanded">#commanded</a> <a href="/archive.html#testing">#testing</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>This post is self documentation as I work through my thoughts on testing an Event Sourced System using <a href="https://github.com/commanded/commanded"><code class="language-plaintext highlighter-rouge">commanded</code></a>. Transitioning from a history of traditional CRUD to Event Sourcing is wild and involves a big change in thinking…<em>I think</em>.</p>

<h2 id="testing">Testing</h2>
<p>Why do I need this? Because I started writing tests like normal, only to start seeing tons of connection and PID related errors. After much discovery and the  <a href="https://elixir-lang.slack.com/archives/CCAB0AYTU">Elixir Slack #commanded</a> channel’s input, I’ve started to grok it. I kind of like how structured it is 🙊</p>

<h2 id="general-config">General Config</h2>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/test.exs</span>
<span class="c1"># Sandbox Repo</span>
<span class="n">config</span> <span class="ss">:app_name</span><span class="p">,</span> <span class="no">AppName</span><span class="o">.</span><span class="no">Repo</span><span class="p">,</span>
  <span class="ss">pool:</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="p">,</span>
  <span class="err">…</span>

<span class="c1"># In memory Event Store</span>
<span class="n">config</span> <span class="ss">:app_name</span><span class="p">,</span> <span class="no">AppName</span><span class="o">.</span><span class="no">EventStore</span><span class="p">,</span>
  <span class="ss">serializer:</span> <span class="no">Commanded</span><span class="o">.</span><span class="no">Serialization</span><span class="o">.</span><span class="no">JsonSerializer</span><span class="p">,</span>
  <span class="ss">adapter:</span> <span class="no">Commanded</span><span class="o">.</span><span class="no">EventStore</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">InMemory</span>

<span class="c1"># Oban in manual mode - jobs only execute with direct invocation</span>
<span class="c1"># I've really liked the `Oban.Testing.with_testing_mode(:inline, fn -&gt; … end)` for individual tests</span>
<span class="n">config</span> <span class="ss">:app_name</span><span class="p">,</span> <span class="no">Oban</span><span class="p">,</span> <span class="ss">testing:</span> <span class="ss">:manual</span>
</code></pre></div></div>

<h3 id="pure-unit-tests---aggregates--business-logic">Pure Unit Tests - Aggregates &amp; Business Logic</h3>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="no">ExUnit</span><span class="o">.</span><span class="no">Case</span><span class="p">,</span> <span class="ss">async:</span> <span class="no">true</span>
</code></pre></div></div>
<p>These are all pure functions and should encompass commands, events, and aggregates. Construct commands, construct state, execute the command, assert on the result.</p>

<ul>
  <li>No Side Effects</li>
  <li>Do Not Assert on the State of the Aggregate</li>
  <li>Only input and output, leave the aggregate state opaque</li>
  <li>This should encompass as much business logic as possible</li>
  <li>Functional Phoenix Components could fit here too</li>
</ul>

<h3 id="unit-tests---eventhandlers">Unit Tests - EventHandlers</h3>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="no">AppName</span><span class="o">.</span><span class="no">DataCase</span><span class="p">,</span> <span class="ss">async:</span> <span class="no">true</span>
</code></pre></div></div>
<p>These are functions with side effects, in Phoenix DDD we would call these context functions. We are testing 1 function that usually performs a DB operation, not the flow of data through the system.</p>

<h4 id="essential-business-element">Essential Business Element</h4>
<p>Test independent parts of the system that have side effects. Test <code class="language-plaintext highlighter-rouge">Oban.Testing.perform_job/2</code>, but not how it links to parts of the system. Test <code class="language-plaintext highlighter-rouge">EventHandler</code>s, often it’s <code class="language-plaintext highlighter-rouge">Event</code> in, assert <code class="language-plaintext highlighter-rouge">:ok</code>, and <code class="language-plaintext highlighter-rouge">assert_enqueued/1</code>. We often have a handler setup that only queues the Oban Job for durable execution. This keeps things very isolated at the cost of an additional step in the flow.</p>

<h4 id="essential-technical-element">Essential Technical Element</h4>
<p>These tests can take advantage of <code class="language-plaintext highlighter-rouge">Ecto.Adapters.SQL.Sandbox</code> because each test can checkout a single connection pool to the DB for that process alone. You can test the internal functions of a Genserver, but testing from an external caller that is going to spin up a new process is asking for trouble. The result is frequent ownership errors or PIDs shutting down before the call returns, making tests very noisy.</p>

<ul>
  <li>Single Process Side Effects (DB)
    <ul>
      <li><a href="https://github.com/oban-bg/oban">Oban</a> Job performs</li>
      <li><a href="https://github.com/oban-bg/oban">Oban</a> Job Enqueued</li>
      <li>Handler responds to the correct events</li>
      <li>Projectors getting called directly</li>
      <li>API calls to 3rd parties</li>
    </ul>
  </li>
  <li>Test Contexts Independently</li>
  <li>This can even include controller functions and Phoenix Liveviews as long as it doesn’t include Command <code class="language-plaintext highlighter-rouge">dispatch</code></li>
</ul>

<h3 id="integration-tests---command-dispatch">Integration Tests - Command Dispatch</h3>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">use</span> <span class="no">AppName</span><span class="o">.</span><span class="no">ConnCase</span>

<span class="c1"># conn_case.ex</span>
  <span class="err">…</span>
  <span class="n">setup</span> <span class="n">tags</span> <span class="k">do</span>
    <span class="n">reset_commanded</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span>
    <span class="no">Cove</span><span class="o">.</span><span class="no">DataCase</span><span class="o">.</span><span class="n">setup_sandbox</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="ss">conn:</span> <span class="no">Phoenix</span><span class="o">.</span><span class="no">ConnTest</span><span class="o">.</span><span class="n">build_conn</span><span class="p">()}</span>
  <span class="k">end</span>

  <span class="c1"># Drops active events and handlers and restarts the app - and in memory event store</span>
  <span class="k">def</span> <span class="n">reset_commanded</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">tags</span><span class="p">[</span><span class="ss">:async</span><span class="p">]</span> <span class="k">do</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">_</span><span class="p">}</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">ensure_all_started</span><span class="p">(</span><span class="ss">:app_name</span><span class="p">)</span>
      <span class="n">on_exit</span><span class="p">(</span><span class="k">fn</span> <span class="o">-&gt;</span> <span class="no">Application</span><span class="o">.</span><span class="n">stop</span><span class="p">(</span><span class="ss">:app_name</span><span class="p">)</span> <span class="k">end</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>
</code></pre></div></div>
<p>Because Event Sourcing with <code class="language-plaintext highlighter-rouge">commanded</code> is an eventually consistent and multi-process affair, we quickly jump to any other type of test being a full, synchronous integration test. These tests require shared DB access because event handlers need to be able to run in the same DB transaction (eg. Reading data that was written by a separate process). Additionally, these require your <code class="language-plaintext highlighter-rouge">commanded</code> app to be running and at the end of the test we need to shut it down or else suffer noisy error logs as handlers are still running after the test process has exited. ⚠️ These tests are significantly slower. For that reason, we should use these for strictly testing the connections between our processes.</p>

<ul>
  <li>Multi-process, soup-to-nuts, integration testing</li>
  <li>LiveView UI -&gt; Command Dispatch -&gt; Projection Update -&gt; LiveView Update</li>
  <li>Ideal Test
    <ul>
      <li><a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html">LiveView Test</a>, <code class="language-plaintext highlighter-rouge">render_click</code> on a submit button</li>
      <li>Await a pubsub message from the projection</li>
      <li>Assert the view changed</li>
    </ul>
  </li>
</ul>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="elixir" /><category term="phoenix" /><category term="event-sourcing" /><category term="commanded" /><category term="testing" /><summary type="html"><![CDATA[Wrapping my head around testing Event Sourced systems #elixir #phoenix #event-sourcing #commanded #testing]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/commanded-logo.png" /><media:content medium="image" url="https://sylverstudios.dev/assets/commanded-logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Looking Beyond Elm: Why LiveView Is My Next Tool of Choice</title><link href="https://sylverstudios.dev/blog/2025/01/29/looking-beyond-elm.html" rel="alternate" type="text/html" title="Looking Beyond Elm: Why LiveView Is My Next Tool of Choice" /><published>2025-01-29T17:00:00+00:00</published><updated>2025-01-29T17:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/01/29/looking-beyond-elm</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/01/29/looking-beyond-elm.html"><![CDATA[<p>LiveView and why I’m excited to move forward. <a href="https://hexdocs.pm/phoenix_live_view/welcome.html"><img src="/assets/phoenix.jpeg" alt="LiveView logo" class="excerpt-image" /></a><a href="/archive.html#elixir">#elixir</a> <a href="/archive.html#liveview">#liveview</a> <a href="/archive.html#phoenix">#phoenix</a> <a href="/archive.html#elm">#elm</a> <!-- Ends the excerpt text, it includes the image --></p>

<p>This is a reflection on my <a href="/blog/2025/01/12/elm-after-7-years.html">love letter to Elm</a>, to make myself better understand why I’m ready to move forward. I’ve been watching <a href="https://hexdocs.pm/phoenix_live_view/welcome.html">LiveView</a> for a long time and it’s passed my threshold as an Elm holdout by becoming mature enough to depend on, easy to develop with, and a leader in showcasing the benefits of modern <a href="https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering">SSR</a>. As my goals grow and change, I need my toolkit to as well. I’m looking forward at more experimentation and exploration with LiveView as the medium. My hypothesis is that <strong>LiveView’s simplified API, 2-way communication, and end-to-end UI testing makes it the best tool for modern use cases</strong> and I’m stoked to see just how much there is to gain.</p>

<p>I’m excited to push LiveView to explore its potential. The shift to SSR and a unified language for frontend and backend unlocks some incredible benefits. <strong>By removing the separate frontend language and active development of the API layer, the <a href="https://minds.md/zakirullin/cognitive">cognitive load</a> to create a product is dramatically reduced.</strong> This removes hundreds of lines of code I’ve come to accept in the past and simplifies the mental model of data flow. These advantages are a result of comfortable SSR templating (<a href="https://hexdocs.pm/phoenix/components.html#heex">heex</a>) and tight websocket integration, which make the client experience nearly indistinguishable from a traditional SPA. Seamless, real-time updates with server push allow SSR to compete directly with modern JS-driven SPAs. In Elixir, process-driven concurrency makes leveraging these benefits even easier. LiveView assigns an in-memory process to each connected client, allowing seamless communication by sending messages directly to these processes using Elixir’s built-in primitives—no special DSL or complex logic required. These all reduce cognitive load and reduce code, which means fewer and faster decisions. Finally, this means faster implementation and better iteration speed.</p>

<p>To be fair to Elm, I’m going to miss it and its unparalleled strengths. The truly unique degree of confidence provided by its error-proof runtime and compile-time checks made it a delight. <strong>Elm excels in building complex UIs in API driven environments.</strong> But for my next chapter, I’m focused on exploring new territory and delivering innovative products, where LiveView aligns perfectly with fast iterations over the stability I loved from Elm.</p>

<p>Every tool has its place, and <strong>LiveView is the right choice for where I’m headed.</strong> Elm is an excellent choice for many scenarios, but my joy comes from providing services that users love, and discovery requires iteration. I’m confident with LiveView I can reduce iteration time, yielding more cycles, faster feedback, and ultimately more progress in less time. I’m excited to see how far LiveView and I can go on this journey.</p>

<blockquote class="note">
  <p>Have thoughts to share? Want to learn more details? I’d love to hear from you! <a href="mailto:aaron.a.votre@gmail.com">Send me an email</a></p>
</blockquote>

<p><em>You know em’, you love em’, my editors: <a href="https://github.com/rjdellecese">RJ Dellecese</a> and <a href="https://github.com/samgqroberts">Sam Roberts</a></em></p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="elixir" /><category term="liveview" /><category term="phoenix" /><category term="elm" /><summary type="html"><![CDATA[LiveView and why I’m excited to move forward. #elixir #liveview #phoenix #elm]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/phoenix.jpeg" /><media:content medium="image" url="https://sylverstudios.dev/assets/phoenix.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What I learned from 7 years of Elm Development</title><link href="https://sylverstudios.dev/blog/2025/01/12/elm-after-7-years.html" rel="alternate" type="text/html" title="What I learned from 7 years of Elm Development" /><published>2025-01-12T11:00:00+00:00</published><updated>2025-01-12T11:00:00+00:00</updated><id>https://sylverstudios.dev/blog/2025/01/12/elm-after-7-years</id><content type="html" xml:base="https://sylverstudios.dev/blog/2025/01/12/elm-after-7-years.html"><![CDATA[<p>My development perspective on using Elm, and when it will serve you. <a href="https://elm-lang.org/"><img src="/assets/elm/elmLogo1.png" alt="Elm-lang logo" class="excerpt-image" /></a><a href="/archive.html#elm">#elm</a> <!-- Ends the excerpt text, it includes the image --></p>

<p><strong>Note about Shamshirz</strong></p>

<p><em>This post reflects my experience working extensively with <a href="https://elm-lang.org/">Elm</a> at a <a href="http://corvusinsurance.com/">B2B insurance startup</a> from 2017 to acquisition in 2024, as the 2nd Engineer.</em></p>

<p>I’m looking at a new year of big changes and I want to share my appreciation and advocacy through reflection. <strong>Elm is my Gold Standard, best in class UI development kit.</strong> I had experience with SSR templating languages, vanilla JS sites, and React just prior to picking up Elm. Elm was my first introduction to a pleasant type systems (👎 unlike Java). The type system is friendly, clarifies thinking, and provides the best feedback loop I’ve experienced in engineering. I learned that Elm can be rocket fuel and I hope to articulate when I saw it accelerate us as well as when I saw the signs that it may not be the best fit. Elm isn’t without its limitations, but allow me to convince you why it’s worth it, dear reader.</p>

<h2 id="why-elm-shines">Why Elm Shines</h2>
<p>The type system is simple and delightful. The compiler is fast, capable, and descriptive enough to be a coding assistant without the need of an LLM. Problem solving is mostly modeling types to reflect your needs and the code writes itself. The initial hurdle is adapting to the syntax and the flow of data through the Model, Update, and View functions. The language is small! Once you’re past the basics, you’ll find it easy—and fun—to dive into discussions and improve your work. In contrast, the JS ecosystem often demands familiarity with language oddities, dependency sprawl (<a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident">leftpad</a>), and each author’s unique style. Elm avoids this entirely. A standard formatter ensures consistent, readable code across all projects. The compiler’s precision was so effective that we rarely felt the need to write tests.</p>

<p><strong>Running Elm in production genuinely changed how I saw front end development.</strong> The compiler’s feedback was the end of the loop, rather than manual UI testing after a change. The compiler will assert whether you’ve covered every possible case and handled every operation that could fail. The implications are enormous and easily overlooked. No debugging in the browser locally, in Staging, in Production, or trying to reproduce a problem a user saw in their browser. It’s very hard to truly appreciate the lack of something found in many little moments, but the overall impact was profound. I didn’t view UI work as tedious or error prone. The only negative that I experienced was that Elm became “boring”;it didn’t need me. I had complete confidence that Elm could guide new engineers to success and adding my experience wouldn’t bring any added benefit. I subscribe to Dan McKinley’s ideal that <a href="https://mcfunley.com/choose-boring-technology">“boring technology”</a> is a good thing that allows you to put energy elsewhere. We know when Elm shines and we know it’s failure modes quite well. Elm’s static nature and tiny language size make it surprisingly “boring” without the need for dozens of years of use like Postgres. The important decision to make is whether you are willing to accept the trade-off that Elm requires in order to benefit.</p>

<h2 id="trade-offs-to-consider">Trade-offs to consider</h2>
<p>Elm is wonderful because of its limitations. One amazing property of Elm is that it guarantees zero runtime errors. To provide that guarantee you aren’t allowed allowed to integrate JS directly into the Elm runtime. Everything external to the runtime has to go through a <a href="https://guide.elm-lang.org/interop/ports">Port</a>. This is doable, but the code that ports require is not pretty and is not fun to work with. We implemented them very infrequently in part because we didn’t need as much from outside of Elm as we expected, but undoubtedly also because of the difficulties of working with them. We got by with simple ports for GraphQL subscriptions and page scrolling, but as others benefit from things like WebSockets and you miss out, you will notice the inaccessibility of advancements made outside of Elm.</p>

<p><strong>We ran into issues occasionally, but compared to experiences with other frameworks it was a <em>much</em> smaller drag on our productivity.</strong> We had a compiler slowdown that impacted development, but <a href="https://x.com/evancz?lang=en&amp;mx=2">Evan Czaplicki</a>, the creator of Elm, gave us some input to resolve it. We got massive benefits from the <a href="https://github.com/dillonkearns/elm-graphql"><code class="language-plaintext highlighter-rouge">elm-graphql</code></a> library (generates an Elm SDK for our GraphQL API), but it was a big implementation cost and still isn’t intuitive enough for engineers to grok quickly.</p>

<p>For Elm or any UI framework, the accidental complexity, <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet">popularized by Fred Brooks</a>, that comes with a separate build system for UI should to be considered. I continue to learn this lesson regularly. This is maintenance and time to your build pipeline. This was not obvious to me when we started with a SPA build step, but since the rise of Elixir LiveView I can’t stop seeing the added burden of a separate UI framework.</p>

<h2 id="1010">10/10</h2>
<p>If the limitations are acceptable for the project I’m working on, then Elm is my preferred way to develop UI. When I prototype, it’s most often with Elm. I spend my time thinking through the types and what I really want to accomplish rather than chasing around errors. It folds well into my dev environment too. I use (and adore) the <a href="https://www.cursor.com/">cursor</a> editor, and with Elm’s compiler, I can immediately feed it back to the LLM and I generally get away with having a conversation with the composer tool and no coding needed 🤯.</p>

<p>Elm is a walled garden, for better or for worse. I love that it hasn’t needed any changes, bug fixes, or updates to keep working perfectly. It feels frozen in amber, in a perfect working state. You can achieve business and personal success with Elm. It got us to an acquisition and the reliability and dev satisfaction far out-weighed the limitations. <strong>If you’re an engineer seeking a delightful UI experience, I can’t recommend Elm enough.</strong> Especially if you are new to <a href="https://en.wikipedia.org/wiki/Functional_programming">functional programming</a> or <a href="https://stackoverflow.com/questions/1517582/what-is-the-difference-between-statically-typed-and-dynamically-typed-languages">static typing</a>, this is a beautiful way to learn.</p>

<blockquote class="note">
  <p>Have thoughts to share? Want to learn more details? I’d love to hear from you! <a href="mailto:aaron.a.votre@gmail.com">Send me an email</a></p>
</blockquote>

<p><em>my editors: <a href="https://github.com/rjdellecese">RJ Dellecese</a> and <a href="https://github.com/samgqroberts">Sam Roberts</a></em></p>]]></content><author><name>Aaron</name></author><category term="blog" /><category term="elm" /><summary type="html"><![CDATA[My development perspective on using Elm, and when it will serve you. #elm]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/elm/elmLogo1.png?v=1" /><media:content medium="image" url="https://sylverstudios.dev/assets/elm/elmLogo1.png?v=1" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Fly.io and Phoenix</title><link href="https://sylverstudios.dev/blog/2022/11/19/fly-and-phoenix.html" rel="alternate" type="text/html" title="Fly.io and Phoenix" /><published>2022-11-19T02:02:00+00:00</published><updated>2022-11-19T02:02:00+00:00</updated><id>https://sylverstudios.dev/blog/2022/11/19/fly-and-phoenix</id><content type="html" xml:base="https://sylverstudios.dev/blog/2022/11/19/fly-and-phoenix.html"><![CDATA[<p>Experimenting with <a href="https://fly.io">Fly.io</a> and <a href="https://www.phoenixframework.org/">Phoenix</a>. <a href="https://fly.io"><img src="/assets/flyBrandmark.svg" alt="Fly.io Brandmark" class="excerpt-image" /></a></p>

<!--more-->

<p>When I explore an idea, I want to test it out all the way through deployment. There is no better way to challenge my assumptions about libraries (comparing HTTP clients) or ideas (SQLite is the future of storage) than working through publishing. I dread this step because it’s either 💰, headaches or hard work. I can’t speak highly enough of <a href="https://ngrok.com">ngrok</a> when I only need to share with friends or treat myself as a real user rather than localhost. I want to go a step further. <a href="https://fly.io/blog/">Fly’s blog</a> has been posting some fantastic material lately and their “inbound” marketing caught me.</p>

<p>My default had been Heroku, RIP 🪦 free tier, but I never loved the workflow. Specific Elixir alternatives like <a href="https://www.gigalixir.com">Gigalixir</a> are out there but didn’t catch my attention. On the other end of the spectrum, AWS fills me with mixed feelings of terror and boredom. I hadn’t found a compelling example until Fly.</p>

<p>Making an account and creating the starter app were easy, guided experiences. That’s how it should be, and better than usual, I actually felt equipped to step away from the guide and try on my own. The <code class="language-plaintext highlighter-rouge">fly</code> CLI is fantastically simple, with great feedback especially while deploying.</p>

<p>“Speed Run” <a href="https://fly.io/docs/speedrun/">Fly Docs</a></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>flyctl
fly auth signup
fly auth login
fly launch <span class="nt">--image</span> flyio/hellofly:latest
</code></pre></div></div>

<p>I budgeted a lot more headaches than I experienced and that convinced me. I had the mental bandwidth left to pursue my other explorations immediately. I experimented with <a href="https://hexdocs.pm/floki/Floki.html"><code class="language-plaintext highlighter-rouge">floki</code></a>, <a href="https://github.com/wojtekmach/req"><code class="language-plaintext highlighter-rouge">req</code></a>, <a href="https://github.com/phoenixframework/phoenix_live_view">Liveview</a>, and <a href="https://github.com/elixir-sqlite/ecto_sqlite3">SQLite</a>. Instead of fighting with deployment, I explored those tools in a real app and the extra energy came from Fly providing the platform. If you’re interested in seeing any of those, or just a Fly deployment in the wild, the <a href="https://github.com/shamshirz/speed">repo lives here.</a></p>

<p>Fly is my new default playground for exploration and full deployment isn’t a <code class="language-plaintext highlighter-rouge">Maybe</code> anymore. I’m stoked to add deployment satisfaction to my normal exploration flow. It used to be such a bother I would skip the joy that comes with seeing real traffic. In hindsight, “no duh” that a tedious deployment process would cause me to lose motivation. #AppCareIsSelfCare</p>

<h3 id="ps"><em>P.S.</em></h3>

<p>The experience brought up another feeling. Cognitive dissonance around full-stack applications. I enjoy Elixir, Fly is awesome, I adore Elm, <a href="https://github.com/dillonkearns/elm-graphql"><code class="language-plaintext highlighter-rouge">elm-graphql</code></a> is a godsend, and I see the value in Liveview. That said, I struggle to rationalize full-time servers and DB instances in the face of how accessible serverless functions + static sites have become. For my needs, 24/7 service is a waste. I’m exploring these ideas now…</p>

<h4 id="pps"><em>P.P.S.</em></h4>

<ul>
  <li>I’m not affiliated with Fly.io, I just really them.</li>
  <li>Shout out to my editors 🙇‍♂️ - <a href="https://github.com/rjdellecese">RJ Dellecese</a> and <a href="https://github.com/samgqroberts">Sam Roberts</a></li>
</ul>]]></content><author><name>Aaron</name></author><category term="blog" /><summary type="html"><![CDATA[Experimenting with Fly.io and Phoenix.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://sylverstudios.dev/assets/flyBrandmark.svg" /><media:content medium="image" url="https://sylverstudios.dev/assets/flyBrandmark.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>