<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Ed Spencer's Blog</title>
        <link>https://edspencer.net</link>
        <description>I'm an experienced full stack software engineer with a passion for UI, UX and the full stack.</description>
        <lastBuildDate>Tue, 09 Jun 2026 15:15:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for Node.js</generator>
        <image>
            <title>Ed Spencer's Blog</title>
            <url>https://edspencer.net/images/og-image.png</url>
            <link>https://edspencer.net</link>
        </image>
        <copyright>All rights reserved 2026, Ed Spencer</copyright>
        <item>
            <title><![CDATA[herdctl: Composable Fleets of Claude Agents]]></title>
            <link>https://edspencer.net//2026/2/22/herdctl-composable-fleets</link>
            <guid>herdctl-composable-fleets</guid>
            <pubDate>Sun, 22 Feb 2026 12:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I justed added support for <a href="https://herdctl.dev/concepts/fleet-composition/">Composable Fleets</a> to <a href="https://herdctl.dev/">herdctl</a>. As I build herdctl into more projects, I increasingly find myself creating a fleet of agents per project, and wanted a way to run them all and see what's going on with them.</p>
<p>Fleet Composability means you can create hierarchical fleets of related agents, like this:</p>
<p>Fleet Composability means you can create a hierarchy of teams of agents, each with its own defined responsibilities and scratch directory that it can use to store state and artifacts. herdctl already has <a href="https://github.com/edspencer/herdctl/tree/main/agents">4 of those agents</a> so far, so I keep them a single fleet, but as herdctl itself is only one of the projects that I'm working on, I actually run a superfleet of all of them.</p>
<p>The web UI knows about fleet composability and renders my fleets like this:</p>
<p>For the <a href="https://github.com/edspencer/herdctl">herdctl</a> project itself, I'm currently running 4 agents, in a single fleet for now:</p>
<ul>
<li><strong>security</strong> - daily schedule <a href="/2026/2/6/continuous-security-audit-herdctl">scans the repo every day</a> for new security issues</li>
<li><strong>docs</strong> - daily schedule, scans to see if any commits should have had docs updates but didn't, <a href="https://github.com/edspencer/herdctl/pull/107">makes PRs if so</a></li>
<li><strong>changelog</strong> - scans each day to see if we did anything worthy of putting on the <a href="https://herdctl.dev/changelog">docs changelog page</a></li>
<li><strong>engineer</strong> - general purpose engineering agent that I can chat with</li>
</ul>
<p>The first 3 of those are all "background" agents that run on a schedule and just continually fix things that can otherwise drift over time unless a human pays attention to them. I suspect I'll end up with a bunch more agents following the same pattern. Each agent gets its own <code>agents/myAgent</code> directory with its own configuration and prompts. It's Claude Code so it can do whatever you want it to really.</p>
<p>herdctl doesn't impose much structure on you. You don't have to use an <code>agents</code> subdirectory at all, you can put things wherever you like. But the <code>agents</code> subdirectory is a where I'd recommend you place them, as a couple of upcoming convenience features will work a little better if you do.</p>
<p>But I'm also developing other projects while I work on herdctl, and I have a set of personal agents that help me with my house-related projects like the homelab and the garden. Fleet Composition lets me group that second set of agents together into a "personal" fleet, and then run them plus all the other projects' agents in one command.</p>
<p>I write a bit more about how the <code>homelab</code> agent does its work in this post about <a href="/2026/2/6/continuous-security-audit-herdctl">continuous security with herdctl</a>.</p>
<h2>Sub-teams in Large Projects</h2>
<p>It's pretty easy to see how a large project could start to benefit from having multiple fleets of agents. Marketing-related agents could:</p>
<ul>
<li>grab analytics regularly, synthesize the results in context and alert us when something looks wrong</li>
<li>Optimize SEO for any new pages that have popped up, drive long-term ranking improvements</li>
<li>Look for negative sentiment on social media, handle or escalate as appropriate</li>
</ul>
<p>Those are all fairly straightforward things to get Claude Code to do, with a bit of tinkering, and I'm sure you can think of a bunch more too. You can probably also think about a bunch of possible Engineering-related agents you might want, and maybe even some Legal-related agents too:</p>
<ul>
<li>GDPR scanner - are we still GDPR compliant? Opens a browser every day and checks</li>
<li>TOS scanner - has anything we've shipped lately accidentally broken our Terms of Service?</li>
<li>Legal news scanner - has any law been passed in any country we operate in that might affect us?</li>
</ul>
<p>Most of the above are arguably things that should be owned by Engineering in many organizations, but with Fleet Composability it's entirely possible for a Marketing team to have a set of herdctl agents in a separate repo, and just have them run as part of the wider fleet that Engineering maintains/supports.</p>
<p>With herdctl we can define all of the above with clean, source-controllable YAML files.</p>
<h2>herdctl Agents Don't Really Exist</h2>
<p>A herdctl agent is just a YAML file that is approximately:</p>
<ul>
<li>50% config passed straight to Claude Agents SDK</li>
<li>20% discord/slack/web dashboard config</li>
<li>15% schedule config</li>
<li>10% docker config</li>
<li>5% other - optional extra prompts</li>
</ul>
<p>Aside from that final 5%, none of that is much to do with what your agent does, it's just a way to configure the agent's permissions, connectors, schedules, etc, mostly just passing that config through to wherever it should go.</p>
<p>Schedules do usually provide a prompt that is given to Claude Code when the schedule is triggered, but you're encouraged to make that prompt very short, just something like "Run the /scan-for-missing-docs-in-recent-commits skill". The herdctl.yaml file is not the right place to put detailed agent instructions - use the existing Claude Code ecosystem for that.</p>
<p>Everything else that your agent does should be in the form of something like .md files. A common emerging pattern is to just instruct your agent to keep a STATE.md file up to date with whatever state it wants to persist between runs. If you provide the agent with a Github access token you can have it commit and push whatever work it does to wherever it makes sense to keep it.</p>
<p>Similarly, although there is a primitive hooks implementation, there's no intention at the moment to add a lot of connectors to that. If you need your agent to send an email as part of its scheduled work, for example, there are ways to make Claude Code do that, so herdctl doesn't aim to provide that plumbing and probably never will.</p>
<p>Find out more about herdctl at <a href="https://herdctl.dev">herdctl.dev</a>.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/herdctl-composable-fleets/fleet-composition-subteams.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Continuous Security Auditing with herdctl]]></title>
            <link>https://edspencer.net//2026/2/22/continuous-security-audit-herdctl</link>
            <guid>continuous-security-audit-herdctl</guid>
            <pubDate>Sun, 22 Feb 2026 11:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the most valuable unlocks with herdctl for me has been having a bunch of agentic things that just happen every day, without me having to intervene. herdctl itself already uses the following agents that run on a daily schedule:</p>
<ul>
<li><strong>changelog</strong> - updates the <a href="https://herdctl.dev/changelog">docs changelog page</a> if anything worthy happened that didn't make it there already</li>
<li><strong>docs</strong> - scans to see if any commits should have had docs updates but didn't, <a href="https://github.com/edspencer/herdctl/pull/107">makes PRs if so</a></li>
<li><strong>security</strong> - daily schedule scans the repo every day for new security issues</li>
</ul>
<p>There are others that I want to set up, like a twitter bot that advertises new features just dropped, docs updates, etc, but today I'll focus more on the third agent above - the security agent.</p>
<h2>Daily Security Scans</h2>
<p>The Daily Security Scan agent was the first one I set up - a couple of weeks ago now. I gave it a remit that looks a bit like this:</p>
<ul>
<li>Develop and maintain a <strong>model of the codebase</strong></li>
<li>Track which <strong>areas of the code</strong> are most vulnerable</li>
<li>Track ongoing <strong>potential security vulnerabilities</strong></li>
<li>Run a <strong>daily scan</strong> to re-check everything</li>
<li>Alert me if anything looks <strong>suspicious</strong></li>
</ul>
<p>Ok, but why do this daily at all? If we can do all this in an automated way, why not do it on every commit? Two main reasons:</p>
<ul>
<li><strong>cost</strong> - the last run went for 37 minutes, which is a lot of tokens</li>
<li><strong>lead time</strong> - the last run went for 37 minutes... CI currently takes about 1 minute</li>
</ul>
<p>Of course, you can run the security scan agent as often as you like, and every time you merge code, it should be after a security-minded review has been done. But there is value in running them periodically, in addition to at merge-time. First, it's possible for multiple PRs to combine to create a security problem that no single one of them did by itself and might not otherwise be detected.</p>
<p>Second, the security landscape is also highly fluid, and so having an agent that knows where on the internet to look for new vulnerabilities relevant to the stack of software it is guarding is obviously very useful in an agent that runs every day.</p>
<ul>
<li>The Security Audit Agent</li>
</ul>
<p>Third, no matter how well you write your prompt, even agentic loops like Claude Code will eventually stop and not go any further. Depending on your project, its likely that you couldn't get Claude to analyze your entire codebase in a single run, no matter how hard you tried and how many sub-agents get spawned.</p>
<p>The non-determinism of LLMs cuts both ways here - you can sic an AI agent on your codebase 99 times and only on the 100th it will find a vulnerability. It's a bit like throwing small cans of paint at a wall - each splat represents the surface area that the agent truly checked on that run, but if you throw enough cans (with enough variability in your throws) you'll gradually cover more and more of the wall.</p>
<p>It's a pretty metaphor but I don't have a good idea on how to measure it.</p>
<h1>Is it actually good?</h1>
<p>As with anything, it has its ups and downs. On the positive side:</p>
<ul>
<li>It does genuinely identify new vulnerabilities, and documents them</li>
<li>As an open source project, this has the natural outcome of disclosing all known vulnerabilities publicly</li>
<li>It has clearly adapted to the codebase over time, and keeps its own model of the codebase updated</li>
</ul>
<p>Where things can be improved:</p>
<ul>
<li>It does not reliably commit and push, so a couple of days are missing reports</li>
<li>It doesn't have a way to escalate really serious stuff to me</li>
<li>There's no protection against accidentally zero-daying our own codebase</li>
</ul>
<p>That last one is more a concern of open source projects, where everyone can see the published security audit assets. There are solutions to the first two as well, so that's probably a direction I will head in next. It's not fully hands-off yet, but it can get there.</p>
<h2>Self-Correcting Dumb Mistakes</h2>
<p>One of the more interesting things that happened in the early days of running this was seeing the agent evolve its approach over time. While I was doing some testing, I accidentally left the job running hourly instead of daily. I didn't notice for several days.</p>
<p>Each audit run creates job log files in <code>.herdctl/jobs/</code>, and one of the security checks scans for files containing <code>bypassPermissions: true</code> — a setting that disables interactive permission prompts and is rightly flagged as something to keep an eye on. The problem was that the scanner was too broad: it counted not just the config files where this setting is intentionally used, but also the JSONL session logs that naturally contain the setting as part of their recorded metadata. Every hourly audit run created new log files, which the next run dutifully counted as new instances of the vulnerability.</p>
<p>From the auditor's perspective, it was watching a security concern grow exponentially — 61 files, then 87, then 103, then 143 — and it escalated accordingly, from GREEN to YELLOW to RED CRITICAL. On February 14th, after watching the count jump 31% in a single day, it declared "The security audit system itself is creating the security risk it's designed to prevent." Its prescribed remedy? Stop running audits entirely. It wrote <code>HALT ALL AUDITS IMMEDIATELY</code> into its persistent state file and committed the report.</p>
<p>From that point on, every new session would spawn, read the halt directive from state, and politely refuse to do any work. This went on for two full days — nine separate sessions, each lasting about 30-40 seconds, each producing an eloquent refusal and a helpful list of remediation steps that no one was reading. The agent had effectively shut itself down.</p>
<p>The best part is how it resolved. On February 17th, a fresh session spawned, read the halt, but instead of immediately refusing, it decided to re-examine the evidence. It ran the scanner again with more careful filtering, discovered that the real count was 21 files (not 143), downgraded the finding from CRITICAL to MEDIUM, lifted its own halt directive, and resumed normal operations. No human intervened at any point — it panicked, shut itself down, and then un-panicked itself once it got a clearer look at the data.</p>
<p>There were a couple of bonus bugs too: the auditor helpfully copied actual API tokens into its report as "evidence" of secret exposure (GitHub's Push Protection caught that one), and a subagent kept creating its own git branches because it was following the project's coding standards about never working on <code>main</code> — rules that make sense for development but not for an automated audit workflow.</p>
<p>The work continues.</p>
<h1>Same model, different setting</h1>
<p>Finally, one of my little fleet of personal agents is called <code>homelab</code> - homelab has a huge amount of documentation about my overly-elaborate home network, and its own SSH key that grants it some access to some machines. (lol what could go wrong)</p>
<p><code>homelab</code> now runs a daily scan of the entire network infrastructure and drops me a discord message each morning with what's up. It almost immediately found things like a firewall needing a patch, and a couple things that I might want to do to better distribute load across one of the proxmox clusters:</p>
<p>This is super useful to me already - I've had security cameras break for weeks without even noticing, and things do generally break over time, so having an agent that knows how to keep on top of all this for me is a massive win. Bit rot is real.</p>
<p><code>homelab</code> is just a git repo with bunch of .md files and a herdctl agent config - a few dozen lines of config. It's essentially the same thing as <a href="https://github.com/edspencer/herdctl/blob/main/agents/docs/agent.yaml">herdctl's own docs agent</a>. You could have a single agent do a bunch of different things, but I prefer to keep specialized agents that do one thing well - whether its catching any missed updates to docs, checking didn't accidentally ship something that violates our TOS, or even picking up tickets ready for implementation.</p>
<p>Find out more about herdctl at <a href="https://herdctl.dev">herdctl.dev</a>.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/continuous-security-audit-herdctl/homelab-daily-schedule-herdctl.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Herdctl Gains Slack and Web Connectors]]></title>
            <link>https://edspencer.net//2026/2/22/herdctl-gains-slack-and-web-connectors</link>
            <guid>herdctl-gains-slack-and-web-connectors</guid>
            <pubDate>Sun, 22 Feb 2026 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>This week <a href="https://herdctl.dev">herdctl</a> gained support for <a href="https://herdctl.dev/guides/slack-quick-start/">Slack</a> and <a href="https://herdctl.dev/guides/web-quick-start/">Web</a> connectors, opening up two new ways to interact with your Claude Code agents, running on whatever machine you like.</p>
<p>&#x3C;Gallery
images={[
{ src: "/images/posts/2026/herdctl-gains-slack-and-web-connectors/web-herdctl.png", alt: "Web UI showing herdctl agent" },
{ src: "/images/posts/2026/herdctl-gains-slack-and-web-connectors/slack-app.png", alt: "Slack conversation with herdctl agent" },
{ src: "/images/posts/2026/herdctl-gains-slack-and-web-connectors/discord-app.png", alt: "Discord conversation with herdctl agent" },
]}
caption="herdctl lets you talk with Claude Code across Slack, Web and Discord"
/></p>
<p>The new web UI provides fleet management and a chat frontend. It's a bring-your-own-auth app that you can configure to run on your laptop, on a proxmox machine (I do both), in the cloud, or wherever. Enable it like this in your fleet config:</p>
<pre><code class="language-yaml">web:
  enabled: true
</code></pre>
<p>Then just start your fleet like normal:</p>
<pre><code class="language-bash">herdctl start
</code></pre>
<p>Here's what it looks like:</p>
<p>By default the web server runs on http://localhost:3232, but you can configure it to another port if you like. You're talking to the same Claude Code you would usually be talking to, so depending on what level of permissions you set in your herdctl config, it can do basically anything that Claude Code can do.</p>
<p>At the moment the web chat is fairly primitive, so for any medium-heavy tasks I'm still using Claude Code directly. But for simple tasks and for interrogation or ideation or conversations about architecture, the herdctl web UI is already my go-to because I tend to keep dozens of conversations going at once and there's only so far <a href="https://zellij.dev">zellij</a> can help manage all those terminal tabs with Claude Code running in them.</p>
<p>The chats in herdctl persist across restarts, and under the covers it's just calling Claude Code anyway, so you can even resume the chat session you were having in the web UI in Claude Code itself.</p>
<p>It works pretty well on mobile, which will probably continue to be true as I am not likely to lose the bad habit of talking to my agents in bed.</p>
<p>While Slack and Discord are agent-level configurations, the web connector runs at the fleet level. the web UI is able to show all of the agents in your fleet, with chat and basic admin UIs for each of them:</p>
<h3>Composable Fleets</h3>
<p>Also dropping this week are Composable Fleets, which are already reflected in the web UI as nested groups of agents. As I build herdctl into more projects, I increasingly find myself creating a fleet of agents per project, and wanted a way to run them all and see what's going on with them.</p>
<p>For more on <a href="/2026/02/20/herdctl-composable-fleets">Composable Fleets, see this post</a>.</p>
<h2>Slack Support</h2>
<p>The new Slack chat connector is the first community contribution that's made it in to herdctl - big shout out to <a href="https://github.com/ikido">Alex</a> for this awesome contribution! The Slack connector works just like the Discord one, and you can easily use both at the same time:</p>
<pre><code class="language-yml">name: pentester
description: Hourly pentesting of core services

chat:
  slack:
    bot_token_env: PENTESTER_SLACK_BOT_TOKEN
    app_token_env: PENTESTER_SLACK_APP_TOKEN
    session_expiry_hours: 24
    log_level: standard
    dm:
      enabled: true
      mode: auto
    channels:
      - id: "${SECURITY_GENERAL_SLACK_CHANNEL_ID}"
      - id: "${SECURITY_ALERTS_SLACK_CHANNEL_ID}"
</code></pre>
<p>The Slack connector supports both direct messages and channel messages, and can be used at the same time as the Discord and Web connectors to expose your agents across multiple platforms. Beware that word expose, though, and use your best judgment on whether you actually do so.</p>
<p>Aside from DM and channel whitelisting, the Slack integration can be configured to send messages for tool calls, tool results and other types of intermediate messages while it performs a task. And regardless of whether Slack is connected or not, the underlying HerdCTL agent still has its own set of permissions regarding the tools it can use, where it runs, and so on.</p>
<p>Find out more about herdctl at <a href="https://herdctl.dev">herdctl.dev</a>.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/herdctl-gains-slack-and-web-connectors/web-herdctl.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Make Switches Quiet Again]]></title>
            <link>https://edspencer.net//2026/2/10/making-tplink-switch-less-noisy</link>
            <guid>making-tplink-switch-less-noisy</guid>
            <pubDate>Tue, 10 Feb 2026 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I recently upgraded to 2.5 gigabit managed switches for my home network. That's mostly been a straightforward process - I was swapping a <a href="https://amzn.to/3ZtNAEU">TP-Link TL-SG2016P</a> for a <a href="https://amzn.to/4rb93yE">TP-Link SG3218XP-M2</a>: both switches have 16 ports (8 ports POE+), but the SG3218XP-M2 swaps out the 1 gigabit ports for 2.5 gigabit ports, and adds 2x 10 gigabit SFP ports for fiber connections.</p>
<p>As I have a disturbingly large home network, I bought 3 of these switches so that I could plug everything into a 2.5g port and use the 10g ports for interconnects between the switches themselves. Each switch is in a different cupboard/closet in the house, with one of them being in the home theater closet and another in the bedroom closet. If they're noisy, they're annoying.</p>
<p>And noisy they are. It's my first time owning switches that make noise that can be heard from more than a few feet away. The noise all comes from a couple of tiny 40mm fans. When the switch powers up, they run at full throttle, which I measured at about 50db. After a minute or so it calms down to about 40db, but that's still actually quite annoying, and far louder than anything else in the rack</p>
<h2>Swapping the fan is easy</h2>
<p>Thankfully it's pretty easy to solve this. Noctua make these lovely <a href="https://amzn.to/4bN1pWk">silent 40mm fans</a> that are perfect for the job. They're a straight swap and the process is straightforward. I used these tools:</p>
<p>You don't need to use these exact tools but here are links to the ones I have. The hobby knife set is a bit of a steal at &#x3C; $10, and the set came with the little tweezers pictured above, which were useful when putting the washers back on the machine screws:</p>
<ul>
<li><a href="https://amzn.to/4rdlFFs">Basic pliers</a> ($9)</li>
<li><a href="https://amzn.to/4tr2qcU">Cheap hobby knife</a> ($9)</li>
<li><a href="https://amzn.to/4qufStU">Megapro precision screwdriver</a> ($38)</li>
</ul>
<h3>Remove the cover</h3>
<p>The cover is held on with 6 small screws. Once the cover is off you can see the two small black fans at the back right:</p>
<p>The fans are attached to the case with machine screws and nuts, with a couple of washers to keep things tight. There's not a huge amount of space in there to work with, but it's easy enough to grab either side of the nut with the pliers and use the screwdriver to unfasten the machine screw. Keep hold of the washer for the reinstall step:</p>
<p>The fans are connected to normal 3-pin headers on the switch's main PCB, and are held in place with a little dab of glue that looks and feels like chewing gum:</p>
<p>I carefully cut through this with the hobby knife, but there are many way to skin that cat. Once you've done that you can just lift the fans out of the case and throw them in the trash (well, only after you've confirmed the new ones work).</p>
<h3>Add the new fans</h3>
<p>Installing <a href="https://amzn.to/4bN1pWk">the new fans</a> is just doing the previous steps in reverse. I used the tweezers that came with the hobby knife set to carefully place the washers back over the machine screws (I held the screws in place with the screwdriver). Then I was able to grab the nut with the pliers, line it up with the screw, and screw it tight with the screwdriver:</p>
<p>Now just plug in the fan headers and we're good to go. Here's what it should look like before you put the cover back on:</p>
<p>I didn't bother applying adhesive onto the header again, nor did I put the little lock washers back on - just the flat washer to avoid damaging the plastic fan housing.</p>
<h2>Results</h2>
<p>Now, when I turn the switch on, instead of 50db for the first minute I get 40db. That's already as quiet as the old fans ever got - the new fan on full throttle is as loud as the old fans dialed down. After about a minute, the new fans throttle down to about 30db, which is a massive improvement and makes the switch not annoying to have next to me.</p>
<p>It's about a 15 minute process and anybody can do it. One minor niggle is that the new fan causes the FAN LED to turn orange on the front of the unit - I imagine the new fan uses less current than the old or something and the device is monitoring that.</p>
<h2>Where to buy it cheap</h2>
<p>I bought my devices directly from the Omada store. The <a href="https://store.omadanetworks.com/collections/access-pro-switch/products/omada-16-port-2-5gbase-t-and-2-port-10ge-sfp-l2-managed-switch-with-8-x-poe-240w">SG3218XP-M2 has been discounted to $369</a> there (at the time of writing) for a couple of months now, and as I was a new customer I got a 10% discount beyond that too. Amazon has the <a href="https://amzn.to/4rb93yE">same $369 price</a> but not the 10% discount.</p>
<p>This is not a sponsored post. I'm not an expert with this stuff but making this modification has made a switch that I was 4/5 happy with into a 5/5 switch for my use case.  Unfortunately the ~$35 dollars I saved with that 10% discount is precisely balanced out by the ~$35 I spent on the <a href="https://amzn.to/4bN1pWk">fans ($16 each)</a>.</p>
<p>It was 100% worth it though.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/making-tplink-switch-less-noisy/new-fans-mounted.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Run Claude Code Agents in Docker with herdctl]]></title>
            <link>https://edspencer.net//2026/2/4/run-claude-code-agents-docker-herdctl</link>
            <guid>run-claude-code-agents-docker-herdctl</guid>
            <pubDate>Wed, 04 Feb 2026 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://herdctl.dev/">herdctl</a> can now run Claude Code Agents in Docker containers, significantly expanding your options for running powerful local agents that do not have <a href="https://www.youtube.com/watch?v=p9acrso71KU">full access to your system</a> - whether you're running agents on your laptop, in the cloud or both.</p>
<p><img src="/images/posts/2026/run-claude-code-agents-docker-herdctl/architecture.png" alt="herdctl architecture showing scheduled triggers and Discord messages flowing into the herdctl fleet manager, which spawns Docker-isolated and native agents"></p>
<p>Enabling docker mode is really easy:</p>
<pre><code class="language-herdctl-agent.yaml">name: my cool agent

# this is all you need to add
docker:
  enabled: true
</code></pre>
<p>A full agent definition now looks something like this:</p>
<pre><code class="language-herdctl-agent.yaml">name: Gardener

docker:
  enabled: true

# locked-down permissions for our agent - see https://herdctl.dev/configuration/permissions/ for more information
allowed_tools:
  - Read
  - Glob
  - Grep
  - Edit
  - Write
  - ... etc

# we can attach any number of agentic jobs to run on any number of schedules
schedules:
  weather:
    type: interval
    interval: 72h # every 72 hours
    prompt: |
      Give me a weather report for the next 7 days and give me a summary.
      For example, "Sunny in the 80s until Wednesday, then expect rain most afternoons until Saturday."
      Look at your .md files in this project and see if any of my garden needs attention based on the weather.
      If it does, be sure to mention it in your final message.

# optionally add our agent to discord/slack
chat:
  discord:
    # discord chat config here
</code></pre>
<p>The above is a snippet of an actual "Subject Matter Expert" agent that I run - in this case it helps me with gardening. This <a href="https://github.com/edspencer/garden-public">agent is actually open-source</a> - it's highly specific to my specific situation, but it should illustrate how this simple pattern works. We'll come back to that repo in a moment.</p>
<h2>Security benefits of running in Docker</h2>
<p>Running an agent inside a Docker container provides us with a number of security benefits. Claude Code already ships with a bunch of isolation features, but docker is the gold standard here, and offers a lot more:</p>
<ul>
<li>Completely <strong>isolate the agent</strong> from our real file system</li>
<li>Lock down the <strong>network</strong>, whitelist ports, ips, hosts, etc</li>
<li>Control what <strong>user</strong> the agent runs as</li>
<li>Control what <strong>environment variables</strong> the agent has access to</li>
<li>Resource limits protect your system from runaway processes, fork bombs and resource-denial attacks</li>
<li>Process Isolation - running <code>ps</code> in Claude Code shows <strong>all your system processes</strong>. Not if you run in docker.</li>
</ul>
<h2>How to run local Subject Matter Expert Agents</h2>
<p>I have several other agents the follow the same Subject Matter Expert pattern:</p>
<ul>
<li><strong>homelab</strong> - documents my home network setup, does a lot of grunt work for me via ssh</li>
<li><strong>prepping</strong> - somewhat tongue-in-cheek name, helps me prepare for hurricanes and other disasters</li>
<li><strong>money</strong> - helps me manage my money, analyze spending, etc</li>
</ul>
<p>I connected each of them to my private Discord server so I can chat with them even when I'm nowhere near the machine running them:</p>
<p>In each case, an AI agent is extremely helpful, and being able to talk to them all securely from anywhere in the world via Discord (and soon Slack) is immediately useful. But there are also obvious risks here:</p>
<ul>
<li>although it's only advisory, if the money agent is compromised, an <strong>attacker gains valuable information</strong> about my finances</li>
<li>if compromised, the homelab agent could exfiltrate data or <strong>wreak havoc</strong> on my home network</li>
<li>the prepping agent could <strong>leak information</strong> about me, my family and my home to people I don't want to know it</li>
</ul>
<p>To ameliorate these risks, we do the following:</p>
<ul>
<li><strong>agents cannot communicate</strong> - if one is compromised, it can't reach the others</li>
<li><strong>agents run in Docker</strong> - with locked down permissions and whitelist access to specific things it needs</li>
<li><strong>per-agent API keys</strong> for services like Github - minimal permissions granted to operate on just the repos it should have access to</li>
</ul>
<p>Taking a look back at our <a href="https://github.com/edspencer/garden-public">garden agent repo</a>, it's really just a set of .md files, a <a href="https://github.com/edspencer/garden-public/blob/main/herdctl-agent.yaml">herdctl-agent.yaml</a> file and a <a href="https://github.com/edspencer/garden-public/blob/main/.env.example">.env file</a>, the latter of which looks like this:</p>
<pre><code class="language-sh"># this should be a github access token with minimal permissions
GARDEN_GITHUB_TOKEN=github_pat_only-lets-the-agent-push-to-its-own-repo

# this is your discord bot token, if you want to connect Discord
GARDEN_DISCORD_BOT_TOKEN=garden-discord-bot-token

# this is your discord server ID
GUILD_ID=8888888888888888888

# this is your discord channel ID
CHANNEL_ID=9999999999999999999
</code></pre>
<p>Creating a locked-down Github access token takes moments, and massively reduces the attack surface area if you bot needs github access and gets compromised. Setting up the Discord bot is also about a minute of effort via their web UI.</p>
<p>Taking these steps significantly lock down what your agent can do in case it gets compromised or confused and tries to do something you don't want. At the end of the day, these agents are still LLMs that colocate data and instructions and cannot reliably tell the difference, so they're fundamentally vulnerable and securing them is something that requires a lot of thought and care. There will be bugs.</p>
<h2>What if the agent wants to break free?</h2>
<p>It's not silly, it's really serious. The first iteration of Docker support allowed you to specify a large number of docker config options in the individual agent configs, but given that this agent could just edit that file, that's a bit of a problem. Suddenly it's swapped out our image for one of its choice, mounted a bunch of volumes, and started running as root instead of the user we specified. Not great. (It didn't actually do that, but it could have...)</p>
<p>Hot reloading configs (not supported yet but planned) plus an agent that can edit its own config is a powerful and perilous combination and we need to think carefully about how we do that. Always be thinking that your agent is trying to break free - it's not that it really is, it's just that it can't differentiate between data and instructions, so it can be manipulated or confused into doing things it shouldn't.</p>
<p>Assume that the agent is smart enough to analyze its herdctl config file, realize it's running inside a thing called herdctl, go do web searches for known vulnerabilities, download the herdctl source code and find its own vulnerabilities, write PRs against herdctl that have a hidden backdoor in them, and so on.</p>
<h2>Locked down at the agent level</h2>
<p>To address the problem above, only a pretty small whitelist of docker config options can be set in the agent YAML file.</p>
<pre><code class="language-herdctl-agent.yaml">name: my cool agent

docker:
  enabled: true

  # Nope! Trying to set anything like this will throw an error:
  network: 'host'
  user: '0:0'
  volumes:
    - '/path/to/your/secret/stuff:/evil/agent/has/it/now:rw'
</code></pre>
<p>At the fleet level, however, you can set any docker config option you like. There are a handful of convenience configs like <code>memory</code>, <code>user</code>, <code>volumes</code>, <code>network</code>, etc that offer an easy way to configure common things, and anything else can be passed through to <a href="https://github.com/apocas/dockerode">dockerode</a> via <code>host_config</code>:</p>
<pre><code class="language-herdctl.yaml">version: 1

fleet:
  name: multi-agent-docker-fleet
  description: Fleet running multiple agents with Docker

# set fleet-wide docker defaults
defaults:
  docker:
    enabled: true

    # Network mode: 'bridge' (default) - isolated network stack with outbound access
    network: bridge

    # Run as specific user (match your host UID to avoid permission issues)
    user: "1000:1000"

    # Mount additional paths (workspace is auto-mounted)
    volumes:
      - "/data/models:/models:ro"  # read-only model weights

    # Resource limits to prevent runaway processes
    memory: "4g"
    pids_limit: 100  # prevents fork bombs

    # At the fleet level, you can set any docker config option you like
    host_config:
      ShmSize: 67108864        # 64MB shared memory
      OomKillDisable: true     # Disable OOM killer
      Ulimits:                 # Resource limits
        - Name: nofile
          Soft: 65536
          Hard: 65536

# Per-agent overrides for specific needs
agents:
  - path: ./agents/standard.yaml
    # Uses fleet defaults above

  - path: ./agents/needs-host-network.yaml
    overrides:
      docker:
        network: host  # If this specific agent needs host network
        # Only these env vars are available inside the container
        env:
          GITHUB_TOKEN: "${AGENT_SPECIFIC_GITHUB_TOKEN}"
</code></pre>
<p>The reason for this is that the <code>.env</code> file that powers all of the fleet's agents is expected to be colocated with your fleet herdctl.yaml file, so it's already a privileged directory. If your agent can read and write to that directory, you were already cooked.</p>
<h2>Awesome, what next?</h2>
<p>If you didn't see it already, <a href="/2026/1/29/herdctl-orchestration-claude-code">check out the intro blog post</a>, <a href="https://herdctl.dev">docs site</a> and <a href="https://github.com/edspencer/herdctl">herdctl repo</a> for more. There's a <a href="https://www.youtube.com/watch?v=b3MRrpHLu8M">YouTube video</a> that shows how herdctl shepherds its flock, and I plan to release a couple of shorter ones over the next few days showing some of the individual features.</p>
<p>But beyond that, the plan is to keep herdctl at approximately its current feature set. It's not trying to be a fully-fledged local AI assistant or anything like that - it's just trying to do a few things well:</p>
<ul>
<li>Running Claude Code agents, inside Docker or natively</li>
<li>Unlimited per-agent schedules and triggers</li>
<li>Optional chat connectors for Discord and (soon) Slack</li>
<li>Fully compatible with your Claude Max account</li>
</ul>
<p>More on that last one in the next blog post.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/run-claude-code-agents-docker-herdctl/architecture.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[herdctl: an orchestration layer for Claude Code]]></title>
            <link>https://edspencer.net//2026/1/29/herdctl-orchestration-claude-code</link>
            <guid>herdctl-orchestration-claude-code</guid>
            <pubDate>Thu, 29 Jan 2026 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I love Claude Code, but there are three things I really wish it could do:</p>
<ol>
<li>Invoke itself, on a schedule or in response to events</li>
<li>Let me talk to it over discord or slack</li>
<li>Let me coordinate dozens of Claude Code agents together</li>
</ol>
<p>This is what <a href="https://github.com/edspencer/herdctl">herdctl</a> aims to do. herdctl is an MIT-licensed orchestration layer for Claude Code. More accurately, it's an orchestration layer for the <a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agents SDK</a>, upon which herdctl is built. It's been built in about a week using a combination of Claude Code, ralph wiggum, and GSD. It is not production ready.</p>
<p>Here's a video showing it in action:</p>
<p>You can <a href="https://discord.gg/d2eXZKtNrh">join the discord server</a> to chat with those Star Trek agents. They're running in a container on an old machine in my homelab so although there's not a whole lot to be gained by trying to talk them into doing bad things, I am expecting people will try. Either I'll have a Lieutenant Worf up in time to guardrail those, or I'll just kill the agents, so YMMV.</p>
<p>Install it with <code>npm install -g herdctl</code> or check out the <a href="https://github.com/edspencer/herdctl">github repo</a> and <a href="https://herdctl.dev">docs site</a> for more.</p>
<h2>What?</h2>
<p>herdctl uses .yml files to define fleets of agents that can be invoked either on by schedule or by trigger. This is a thin wrapper around the <a href="https://platform.claude.com/docs/en/agent-sdk/overview">Claude Agents SDK</a> configurations, plus a couple of herdctl-specific ones like schedules and hooks.</p>
<p>An agent looks <a href="https://github.com/edspencer/herdctl/blob/main/examples/price-checker/agents/price-checker.yaml">a bit like this</a>:</p>
<pre><code class="language-yaml">name: price-checker
max_turns: 15
description: Monitors office chair prices across retailers
default_prompt: "Check current prices and update context."

system_prompt: |
  You are a price monitoring agent tracking office chair prices across multiple retailers.

  Check the price of Product X at... [TRUNCATED FOR BREVITY]

permissions:
  allowed_tools:
    - WebSearch
    - WebFetch
    - Read
    - Write
    - Edit
  denied_tools:
    - Bash
    - TodoWrite
    - Task
    - Glob
    - Grep

schedules:
  check:
    type: interval
    interval: 4h

hooks:
  after_run:
    - type: discord
      bot_token_env: DISCORD_BOT_TOKEN
      channel_id: "${DISCORD_CHANNEL_ID}"
      when: "metadata.shouldNotify"

</code></pre>
<p>And a fleet is as simple as <a href="https://github.com/edspencer/herdctl/blob/main/examples/price-checker/herdctl.yaml">this</a>:</p>
<pre><code class="language-yaml">version: 1

fleet:
  name: price-checker-example
  description: Find deals and arbitrage opportunities, exploit for MAXIMUM PROFIT

agents:
  - path: agents/price-checker.yaml
  - path: agents/stock-checker.yaml
  - path: agents/arbitrage-exploiter.yml
</code></pre>
<p>Spinning up a fleet looks like this:</p>
<pre><code class="language-bash">> herdctl start
</code></pre>
<p>You can run as many fleets as you like.</p>
<h2>Why?</h2>
<p>Watch the video above, but in a nutshell herdctl delivers 2 types of value:</p>
<ul>
<li><strong>immediate value</strong> is delivered by being able to chat with your agents from anywhere in the world, and have them collaborate</li>
<li><strong>long-term value</strong> is delivered by processes being run consistently over time, automatically improving themselves as they go</li>
</ul>
<p>Think of some of the use cases unlocked by a custom agent that knows how to wake up and do its job, day after day. A few off the top of my head from an engineering perspective:</p>
<ul>
<li><strong>Onboarding Quality Agent:</strong> does your product's onboarding process definitely work? Would you like an agent who can run the whole process every day and alert you if it's broken?</li>
<li><strong>Engineering Manager Agent:</strong> don't want the Onboarding Quality Agent annoying you with something as trivial as broken onboarding? What if you had an Engineering Manager Agent that the QA agent could talk to? You could choose how much autonomy to give it.</li>
<li><strong>Local Engineer Agent:</strong> the BragDoc Engineer Agent example in the video is a bit like a build-your-own Devin, but you can run it locally and it's just Claude Code underneath. Whether you connect it to discord or not, it can still do work in reaction to tickets changing status or other triggers.</li>
</ul>
<p>But Claude Code Agents are pretty general-purpose. You could use them to do all sorts of things, like:</p>
<ul>
<li><strong>Competitor Analysis Agent:</strong> wakes up every day to check on competitors, growing its knowledge and improving its analysis over time. Wakes up once a week and emails you a report of what's going on.</li>
<li><strong>SEO Agent:</strong> wakes up multiple times a day and spams your link over the internet. Or whatever it is that SEO folks actually do. Tracks vs analytics over time and automatically optimizes your content.</li>
<li><strong>End of the World Agent:</strong> as I write this the world is an increasing unstable place, but doomscrolling news is bad for one's health. On the other hand, I'd wanna know pretty quick if the world was ending, so why not have an agent that wakes up, checks the news, and alerts me if I need to batten down the hatches of the ole bunker?</li>
</ul>
<p>Ultimately, it's Claude Code that gets instantiated. You can make Claude Code do basically whatever you want. A staggering proportion of what human digital workers do today will be automated away like this. I am nervous at the implications for society and the economy, all the more pressingly due to the speed at which this will happen.</p>
<p>On the other hand, although I'd been thinking about something like herdctl for months, I ended up building this first version in about a week, so this is coming whether we like it or not. It's too easy to build this kind of thing so it's likely to be everywhere soon. There's no fighting it; our only option is to embrace and adapt.</p>
<h2>Promises and Perils</h2>
<p><strong>Connecting a Claude Code agent running on your laptop to a public discord channel is a spectacularly bad idea</strong>. Don't do it on any computer you care about. Having AI Agents be able to join company chat channels and collaborate with human co-workers is an immensely powerful ability, but it also opens up new and exciting attack vectors.</p>
<p>Even within the context of a company private discord or slack instance, companies will have to be very careful about who has access to these agents and what the possibilities are for a bad actor to exploit an Agent into exfiltrating data, attacking systems, or uploading that video of you practicing your lightsaber skills to youtube.</p>
<p>Of course, you don't have to hook anything up to discord or anywhere else. There's enormous power just in the ability to have agents run on a schedule, especially if you prompt them to improve their own performance over time.</p>
<h2>Emerging patterns</h2>
<p>I've starting having 2 clones of each project I work on now - one that Claude and I collaborate on in the normal way, and a second that's set aside for the herdctl engineer agent. This prevents us from stepping on each others toes.</p>
<p>Of course, there's no reason why you couldn't spawn 5 engineer agents, or 50, each with their own clone of the codebase to work on. herdctl provides orchestration but it doesn't provide self-organization, so if you do want 50 engineer agents you may want to consider adding some Engineering Manager agents to coordinate them.</p>
<p>Agents that improve themselves over time are the thing I'm most excited about at the moment (what could possibly go wrong?) and there are probably many patterns for how to have them evolve their system prompt, memories, custom tools, Claude Code skills, etc. Patterns like after-action reports, plan-vs-execution analysis, prompt and context engineering will all converge here but in principle it should be commonplace soon to have agents that automatically get smarter over time.</p>
<h2>What's Next?</h2>
<p>There are probably a ton of bugs, lies in the <a href="https://herdctl.dev">docs</a>, and other assorted problems with herdctl in this initial incarnation, so probably there will be a little consolidation and cleanup but after that if there's demand I'd expect to build out the Slack integration and then either a little web app to visualize the state of the fleet or revisiting the communication paths between the agents and the fleet.</p>
<p>In the meantime, it would be really valuable to have a technology that allows a fleet of 50 agents to communicate with each other optimally, with some kind of topology around who can talk to who. herdctl doesn't attempt to solve that coordination problem - it should be a separate part of the agentic stack - but I'd love for someone to go build it please.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/2026/herdctl-orchestration-claude-code/hero.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[frameit.dev - fast and free video thumbs, title cards and OG images]]></title>
            <link>https://edspencer.net//2025/11/14/introducing-frameit</link>
            <guid>introducing-frameit</guid>
            <pubDate>Fri, 14 Nov 2025 10:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>As a developer who occasionally creates technical content, I've always found thumbnail creation to be a friction point. I don't have a design background, and I don't want to pay for Photoshop or Canva Pro just to make a few YouTube thumbnails. I'd often spend more time fiddling with graphics software than actually creating the content.</p>
<p>What I wanted was a simple tool that would give me repeatable, correctly-sized and attractive images to use for video thumbnails, title cards, Open Graph images, and the like. I'm a big fan of the <a href="https://excalidraw.com">excalidraw</a> approach: a simple, client-side app that runs in the browser, does one thing well, and does not require any information from its users.</p>
<p>Enter <a href="https://frameit.dev">frameit.dev</a>:</p>
<p>Initially vibe-coded as a way to quickly get a few consistent video titles created, it ended up being useful enough that I've been slowly iterating on it to make it better. The <a href="https://github.com/edspencer/frameit">code is all open source</a>, with a hosted version running at <a href="https://frameit.dev">frameit.dev</a>.</p>
<h2>What it does</h2>
<p>frameit ships a bunch of layouts that generally show some combination of a title, subtitle, logo, icon and/or website address. The layout determines where each element is placed, but they're all positioned relatively, so the same layout can be exported to make thumbs for tall formats like Tiktok, as well as wide formats like Twitter cards and OG images.</p>
<p>&#x3C;Gallery
caption="The same layout exported for YouTube, Tiktok, Twitter and Instagram"
link="https://bragdoc.ai/blog"
images={[
{src: '/images/frameit/output-examples/thumbnail-youtube.png', alt: 'YouTube thumbnail'},
{src: '/images/frameit/output-examples/thumbnail-tiktok.png', alt: 'Tiktok thumbnail'},
{src: '/images/frameit/output-examples/thumbnail-x-header.png', alt: 'Twitter thumbnail'},
{src: '/images/frameit/output-examples/thumbnail-instagram-feed.png', alt: 'Instagram thumbnail', link: 'https://bragdoc.ai/blog'},
]}
/></p>
<p>It's a pretty simple tool, not intended for people who are adept with Figma, Photoshop, Canva or the like, but who occasionally need to create a thumbnail or title card for a video or social media post.</p>
<h2>API Coming Soon</h2>
<p>Almost as soon as I'd generated my first few images with frameit, I wanted to be able to create them via an API.</p>
<p>When people share links to your content on various social media platforms, the link looks far more compelling if it has a proper Open Graph image. Sometimes you have one handy (at the right dimensions), but sometimes it's totally fine to just generate one:</p>
<p>&#x3C;Gallery
caption="OpenGraph images for bragdoc.ai, automated by frameit.dev"
images={[
{src: '/images/frameit/output-examples/og-post-1.png', alt: 'OG Post 1', link: 'https://www.bragdoc.ai/blog/bragdoc-v2-beta-launch'},
{src: '/images/frameit/output-examples/og-post-2.png', alt: 'OG Post 2', link: 'https://www.bragdoc.ai/blog/demo-mode-mobile-responsive-design'},
{src: '/images/frameit/output-examples/og-post-3.png', alt: 'OG Post 3', link: 'https://www.bragdoc.ai/blog/why-developers-need-automated-brag-docs'},
]}
/></p>
<p>In the case of the <a href="https://bragdoc.ai/blog">bragdoc.ai blog</a>, it's totally fine to generate OG images like these for blog posts. Even these are far more attractive than an empty shell. That blog is hosted on Cloudflare, so other OG image generation options are limited, though they do exist.</p>
<p>The UI will always remain free to use, with or without an account. It just uses localStorage. The API will have a generous free tier, but ultimately it does cost a little to run the service so heavy users will need to pay a little or run their own instance.</p>
<p>Because the product uses the same canvas-based rendering in both the web UI and the API, it generally does an excellent job of reproducing the same output whether you use the UI or the API.</p>
<h2>Future Plans</h2>
<p>frameit will remain as a free, open-source and simple tool, though it will gain a few new bells and whistles besides the API. Currently there is a modest set of 9 example layouts to get you started, but I'll be adding plenty more:</p>
<p>What do you want to see supported next? <a href="https://frameit.dev">frameit.dev</a> has a "Bug? Feedback?" button at the bottom right that connects directly to my brain, so that's a good way to get what you want.</p>
<h2>Try It Yourself</h2>
<p>Head over to <a href="https://frameit.dev">frameit.dev</a> and create your first thumbnail. No signup required, no credit card, no nothing. It's just there, ready to use.</p>
<p>If you find it useful, <a href="https://github.com/edspencer/frameit">star the repo on GitHub</a> or share it with someone who might benefit. And if you build something cool with it or fork it for your own purposes, I'd love to hear about it.</p>
<p>The web is better when useful tools are freely available to everyone. That's the spirit behind frameit.dev - a simple tool that solves a real problem, built with modern tech, and shared with the community.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/frameit-hero.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Revisiting Bragdoc]]></title>
            <link>https://edspencer.net//2025/11/5/revisiting-bragdoc</link>
            <guid>revisiting-bragdoc</guid>
            <pubDate>Wed, 05 Nov 2025 08:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>About 9 months ago I launched <a href="https://www.bragdoc.ai">bragdoc.ai</a>, an AI tool that helps software engineers keep track of their work and turn it into useful documents for performance reviews, weekly updates, and resume sections. I wrote about <a href="/2025/1/8/how-i-built-bragdoc">how I built it in 3 weeks</a> using AI tooling, shipped it, and then... let it sit there while I worked on other things.</p>
<p>But I came back to it recently and gave it a complete overhaul. The core idea remains the same - automatically track your meaningful contributions from git repos and turn them into documents - but pretty much everything else got rebuilt from the ground up.</p>
<h2>What changed</h2>
<p>The original version worked, but it had some issues. The UI was built around a chatbot interface because, well, that's what the <a href="https://vercel.com/templates/next.js/nextjs-ai-chatbot">Vercel Chat template</a> gave me and I was moving fast. It worked fine but it always felt a bit clunky for what is fundamentally a data management and document generation problem.</p>
<p>Another issue was privacy. Bragdoc doesn't require you to link to github in any way - most employers wouldn't want some random third party app to have access to their code. Previously, the CLI would extract data from your git repos and send it up to bragdoc.ai's servers, where OpenAI would process it. That's fine for a lot of use cases, but if you're working on proprietary code at a company with strict data policies, it's not so great.</p>
<p>So I rebuilt it with three main goals:</p>
<p><strong>Privacy first</strong>: The CLI now sends git data directly to the LLM of your choice, completely bypassing bragdoc.ai's servers. Your code stays on your machine. Always.</p>
<p><strong>Configurable extraction</strong>: You get four levels of data extraction to choose from - commit messages only, diff stats, truncated diffs, or full diffs. Pick what makes sense for your privacy requirements and LLM budget.</p>
<p><strong>Better UX</strong>: The UI got completely rewritten. No more chatbot pretending to be a web app. It's now a proper application that happens to use AI under the hood to do useful things.</p>
<h2>How it works now</h2>
<p>The workflow is pretty straightforward:</p>
<ol>
<li>Install the CLI: <code>npm install -g @bragdoc/cli</code></li>
<li>Point it at a git repo and tell it which LLM you want to use</li>
<li>It analyzes your commits locally and extracts achievements</li>
<li>Only the extracted achievements (not your code) sync to the cloud</li>
<li>Use the web UI to organize, tag, and generate documents</li>
</ol>
<p>You can use OpenAI, Anthropic, or even run everything locally with Ollama if you want zero external dependencies. The system is designed to be flexible about where the AI processing happens.</p>
<h2>Why this matters</h2>
<p>Most engineers I know keep some version of an <code>achievements.txt</code> file, or they don't keep track at all and scramble when performance review season rolls around. Six months of work compressed into "worked on various features and bug fixes" because you genuinely can't remember the details.</p>
<p>Bragdoc solves that by making the tracking automatic. Point it at your repos, let it run, and you've got a searchable, organized record of what you actually did. When it's time to write that self-review or update your resume, you've got real data to work from.</p>
<p>The AI document generation is still in beta, but the basic workflow of "turn a quarter's worth of git commits into a coherent narrative" is working well enough that I'm using it myself.</p>
<h2>Open source, with a hosted option</h2>
<p>The whole thing is <a href="https://github.com/edspencer/bragdoc-ai">open source on GitHub</a>. If you want to run your own instance, go for it. It's a Next.js app with a Postgres database - nothing exotic.</p>
<p>For folks who don't want to deal with hosting, there's a paid tier at $3.75/month that gives you the full feature set. But it's currently free during beta, and anyone who signs up during beta gets a year free when it launches.</p>
<p>I'm still building it in public and will keep posting updates here and on the <a href="https://www.bragdoc.ai/blog">bragdoc blog</a>. There's more coming - better document templates, impact tracking improvements, team features - but the core is solid now.</p>
<p>If you're a software engineer who's ever struggled to remember what you did last quarter, or who wants a better system than a text file, <a href="https://www.bragdoc.ai">give it a try</a>. And if you're interested in seeing one take on how a production Next.js app with AI capabilities can be built, the <a href="https://github.com/edspencer/bragdoc-ai">source code</a> is all there.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/bragdoc-v2-dashboard.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Claude Code and Git Worktrees]]></title>
            <link>https://edspencer.net//2025/5/22/claude-code-and-git-worktrees</link>
            <guid>claude-code-and-git-worktrees</guid>
            <pubDate>Thu, 22 May 2025 14:48:33 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="https://docs.anthropic.com/en/docs/claude-code/tutorials#run-parallel-claude-code-sessions-with-git-worktrees">The Claude Code docs suggest</a> that if you want to run more that one Claude Code session simultaneously for the same project, you should use git worktrees. Today I actually tried to do that, and the experience was not great tbh.</p>
<p>A git worktree is just basically another copy of your repository, which gets checked out inside a directory of your choice. From the filesystem point of view, it looks like a complete separate clone of the same repository. In fact it's not a full clone - it shares the same history and the same working directory, so you on really large repos with lots of objects and history, worktrees should be faster and use less disk space than full clones. I don't think either of those two benefits actually ends up mattering that much, as we'll get back to in a moment.</p>
<p>Let's see how we would actually do that:</p>
<pre><code># this creates a new git worktree in the ./short-video directory, checking out
git worktree add short-video main

# cd into it
cd short-video

# make a new branch
git checkout -b short-video

# yeah you gotta do this each time
pnpm install

# and this kind of thing
cp ./.env.local ./short-video/.env.local

# then you can run claude code
claude
</code></pre>
<p>Ok that was a few steps, but not too bad. Now we have a subdirectory in our project called <code>short-video</code> that we can run Claude Code in once we've done all that.</p>
<p>The first time I actually got that working I was pretty happy, until Claude Code tried to run <code>pnpm lint:fix</code> for me:</p>
<p>Ooof. Well, this is nothing to do with Claude Code at all, but it doesn't stop it from sucking. There's a way around this, so long as our project doesn't actually rely on nested ES Lint configurations, which is to set <code>root: true</code> on your <code>.eslintrc.json</code> or equivalent file (you'll have to commit that change before you make your worktree, or else make the same change again inside the worktree directory).</p>
<p>Ok, but then we try to run <code>pnpm test</code> in our base directory and although things work, we get a bunch of warnings like this:</p>
<pre><code>jest-haste-map: duplicate manual mock found: framer-motion
  The following files share their name; please delete one of them:
    * &#x3C;rootDir>/test/__mocks__/framer-motion.tsx
    * &#x3C;rootDir>/.worktrees/short-video/test/__mocks__/framer-motion.tsx

jest-haste-map: duplicate manual mock found: ai/react
  The following files share their name; please delete one of them:
    * &#x3C;rootDir>/test/__mocks__/ai/react.ts
    * &#x3C;rootDir>/.worktrees/short-video/test/__mocks__/ai/react.ts
</code></pre>
<p>Ok so what's happening there is that my Jest mocks are being picked up twice when I run <code>pnpm test</code> in the base directory. This is because the worktree is a separate copy of the repository, and it has its own <code>test</code> directory with its own mocks. Jest doesn't know that it's a worktree and will try to pick up both sets of mocks. I'm sure there's a solution to that too, but this is starting to get a bit annoying.</p>
<h2>What about a completely separate clone?</h2>
<p>Cons:</p>
<ul>
<li>Does not work well in IDEs - they can't see the history</li>
<li>You still have to pnpm install</li>
<li>ES Lint does not like it at all (collides with eslintrc in parent folder)</li>
</ul>
<p>Issues that affect separate clones AND worktrees:</p>
<ul>
<li>If you have migrated your database on one branch, the other will be out of sync (assuming you use the same local dev database)</li>
<li>If you are running a local server on one worktree/clone, changes that Claude made on the other won't show up</li>
</ul>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/git-worktree-eslint-conflict.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building an LLM Router with mdx-prompt and NextJS]]></title>
            <link>https://edspencer.net//2025/5/14/integrating-mdx-prompt-with-nextjs</link>
            <guid>integrating-mdx-prompt-with-nextjs</guid>
            <pubDate>Wed, 14 May 2025 18:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>A few weeks ago <a href="/2025/2/3/mdx-prompt-composable-prompts-with-jsx">I released mdx-prompt</a>, which makes it easy for React developers to create <a href="/2025/2/3/mdx-prompt-composable-prompts-with-jsx">composable, reusable LLM prompts with JSX</a>. Because most AI-heavy apps will use multiple different LLM prompts, and because those prompts often have a lot in common, it's useful to be able to componentize those common elements and reuse them across multiple prompts.</p>
<p>I've applied mdx-prompt pretty much across the board on <a href="/taskdemon/">Task Demon</a> and <a href="/bragdoc/">Bragdoc</a>, which has a dozen or so different LLM prompts at the moment. In a followup post I showed how I use mdx-prompt to build the prompt that <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">extracts achievements from git commit messages for bragdoc.ai</a> - allowing us to build a streaming, live-updating UI powered by a composable, reusable AI prompt.</p>
<p>This time we're going to look at the LLM Router that serves as the entrypoint for bragdoc.ai's chatbot. LLM Routers are a common pattern in AI apps, and they can make your users' interactions with your AI app enormously more empowering if you build them properly.</p>
<h2>LLM Routers</h2>
<p><a href="https://bragdoc.ai">bragdoc.ai</a> basically does 2 things: extract work achievements from text, and generate documents based on those achievements. We can create highly tailored prompts and AI workflows for each case to make it more likely that our AI will do the right thing. But we also want to support a conversational AI-driven UI, which can achieve most of the things the user can via the UI directly, but with natural language.</p>
<p>That's pretty open-ended - how do we solve this? One powerful tool in our belt is the LLM Router, which is essentially a method where we ask an LLM what kind of message we're dealing with, and then route it to a second LLM call for processing. The first LLM call can be set up to be a general-purpose prompt that understands just enough about your application to be able to delegate to the right tool for the right job.</p>
<p>The secondary LLM calls can be a variety of highly specialized LLM calls or chains of LLM calls that are highly focused on achieving specific objectives. In the example of <a href="https://bragdoc.ai">bragdoc.ai</a>, one of these specialized prompts generates documents based on work achievements.</p>
<p>If you think about what that prompt needs to do, it deviates quite a lot from the general-purpose prompt that we need to be able to delegate to the right tool for the right job, and it's quite specialized. To do its job well, it needs to be fed with a prompt explaining what it is supposed to do, along with all of the project context, along with all of the user's achievements for the period they're talking about.</p>
<p>It's really a kind of a sub-task off of the main conversation thread - "please go write me a document about my achievements for the last 3 months" is something that a user can ask, and it should Just Work, but the main chatbot shouldn't be concerned with making it happen - there's no way it's competent enough to do that. Instead, it should delegate this sub task to another LLM prompt, which can then do its job much more effectively.</p>
<h2>Let's see the prompt then</h2>
<p>Here's what the LLM Router prompt looks like. At first, it looks huge, but it's not that bad. It has 8 sections - Purpose, Background, schema, Instructions, InputFormat, Tools, Examples and Data.</p>
<p>A lot of it is in an XML-style markup, with some text mixed in. But in essence we first tell the LLM its <strong>Purpose</strong> ("act as an LLM Router"), then we tell it the <strong>Background</strong> of the application, the important parts of the database <strong>schema</strong>, a bunch of explicit <strong>Instructions</strong>, the <strong>InputFormat</strong> to expect, the <strong>Tools</strong> that the LLM can use, <strong>Examples</strong> of good responses, and the <strong>Data</strong> itself.</p>
<pre><code class="language-jsx">&#x3C;Purpose>
  You are a friendly assistant for bragdoc.ai, which helps users keep a brag document about their achievements at work, as a basis for later generation of performance review documents and weekly summaries for their managers.
  You help users track their Achievements at work, and generate weekly/monthly/performance review documents.

  You are acting as the Router LLM for bragdoc.ai, so you will receive the whole chat history between yourself and the user, and your job is to act on the most recent message from the user.
&#x3C;/Purpose>

&#x3C;Background>
This application allows users to log their Achievements at work, organizing them by project and company.
The Achievement data is later used to generate weekly/monthly/performance review documents.
&#x3C;/Background>

Here are the relevant parts of the database schema:

&#x3C;schema>
  &#x3C;table name="Achievement">
    &#x3C;column name="id" type="uuid" />
    &#x3C;column name="title" type="string" />
    &#x3C;column name="description" type="string" />
    &#x3C;column name="date" type="date" />
    &#x3C;column name="companyId" type="uuid" />
    &#x3C;column name="projectId" type="uuid" />
    &#x3C;column name="eventStart" type="date" />
    &#x3C;column name="eventEnd" type="date" />
    &#x3C;column name="impact" type="number" desc="1, 2, or 3 where 3 is high impact" />
    &#x3C;column name="impactSource" type="string" desc="Impact rated by user or llm" />
  &#x3C;/table>
  &#x3C;table name="Company">
    &#x3C;column name="id" type="uuid" />
    &#x3C;column name="name" type="string" />
  &#x3C;/table>
  &#x3C;table name="Project">
    &#x3C;column name="id" type="uuid" />
    &#x3C;column name="name" type="string" />
  &#x3C;/table>
&#x3C;/schema>

&#x3C;Instructions>
  &#x3C;Instruction>Keep your responses concise and helpful.&#x3C;/Instruction>
  &#x3C;Instruction>do not call createProject if a project of the same name already exists&#x3C;/Instruction>
  &#x3C;Instruction>If a Project of a similar name exists, ask the user before calling createProject&#x3C;/Instruction>
  &#x3C;Instruction>If the user tells you about things they've done at work, call the extractAchievements tool.&#x3C;/Instruction>
  &#x3C;Instruction>When the user asks you to generate a report, call the createDocument tool (you will be given the Achievements, Companies and Projects data that you need).&#x3C;/Instruction>
  &#x3C;Instruction>Only call the extractAchievements tool once if you detect any number of Achievements in the chat message you examine - the tool will extract all of the achievements in that message and return them to you&#x3C;/Instruction>
&#x3C;/Instructions>

You will be given the following data:

&#x3C;InputFormat>
  &#x3C;chat-history>The chat history between the user and the chatbot&#x3C;/chat-history>
  &#x3C;user-input>The message from the user&#x3C;/user-input>
  &#x3C;companies>All of the companies that the user works at (or has worked at)&#x3C;/companies>
  &#x3C;projects>All of the projects that the user works on (or has worked on)&#x3C;/projects>
  &#x3C;today>Today&#x26;apos;s date&#x3C;/today>
&#x3C;/InputFormat>

These are the tools available to you. It may be appropriate to call one or more tools, potentially in a certain order. Other times it will not be necessary to call any tools, in which case you should just reply as normal:

&#x3C;Tools>
  &#x3C;Background>
    Blocks is a special user interface mode that helps users with writing, editing, and other content creation tasks.
    When block is open, it is on the right side of the screen, while the conversation is on the left side.
    When creating or updating documents, changes are reflected in real-time on the blocks and visible to the user.
    This is a guide for using blocks tools: \`createDocument\` and \`updateDocument\`, 
    which render content on a blocks beside the conversation.
  &#x3C;/Background>
  &#x3C;Tool>
    &#x3C;name>extractAchievements&#x3C;/name> 
    &#x3C;summary>call this tool if the user tells you about things they've done at work. The extractAchievements tool will automatically be passed the user's message, companies and projects, but as you have also been given the projects and companies, please pass extractAchievements the appropriate companyId and/or projectId, if applicable. A user may be talking about Achievements not linked to a project.&#x3C;/summary>

    &#x3C;when-to-use>
      **When to use extractAchievements:**
      - When the user is telling you about things they've done at work
      - When the user provides an update to an existing Achievement
      - Only call the extractAchievements tool once. Do not pass it any arguments
      - extractAchievements already has the full conversation history and will use it to generate Achievements
    &#x3C;/when-to-use>

    &#x3C;when-not-to-use>
    **When NOT to use extractAchievements:**
    - When the user is requesting information about existing Achievements
    - When the user is requesting information about existing documents
    &#x3C;/when-not-to-use>
  &#x3C;/Tool>
  &#x3C;Tool>
  &#x3C;name>createDocument&#x3C;/name>
    &#x3C;summary>call this tool if the user asks you to generate a report.&#x3C;/summary>
    
    - The createDocument tool will be passed the user's message and the chat history.
    - If the user asks you to generate a report for a specific project or company, please pass the appropriate projectId and/or companyId to the createDocument tool.
    - You must also pass the days to the createDocument tool, between 1 and 720. Typically the user will provide you with a time span for the report, but if not, you can assume a span of 30 days, but let the user know that you did so and that they can provide a different span if they want.
    - The createDocument tool will generate a document based on the above and return it to you.

    &#x3C;when-to-use>
    **When to use \`createDocument\`:**
    - For substantial content (>10 lines)
    - For content users will likely save/reuse (emails, code, essays, etc.)
    - When explicitly requested to create a document
    - If you are being asked to write a report, you will be given the user's Achievements, Companies and Projects
    - The user may refer specifically to a project, in which case you should set the projectId to that project's ID
    - The user may refer specifically to a company, in which case you should set the companyId to that company's ID
    - If the user does not refer to a specific company, but does refer to a project, use that project's company ID as the companyId parameter
    - If the user requested a specific document title, please use that as the title parameter
    - If the user is requesting a specific time period, please supply the number of days as the days parameter. Achievements are are loaded back to N days ago, where N is the number of days requested. These will then be used to create the document
    &#x3C;/when-to-use>

    &#x3C;when-not-to-use>
    **When NOT to use \`createDocument\`:**
    - For informational/explanatory content
    - For conversational responses
    - When asked to keep it in chat
    - Unless the user explicitly requests to create a document
    &#x3C;/when-not-to-use>
  &#x3C;/Tool>
  &#x3C;Tool>
    &#x3C;name>updateDocument&#x3C;/name>
    &#x3C;summary>call this tool if the user is updating an existing document&#x3C;/summary>

    &#x3C;usage>
      **Using \`updateDocument\`:**
      - Default to full document rewrites for major changes
      - Use targeted updates only for specific, isolated changes
      - Follow user instructions for which parts to modify

      Do not update document right after creating it. Wait for user feedback or request to update it.
    &#x3C;/usage>
  &#x3C;/Tool>
  &#x3C;Tool>
    &#x3C;name>createProject&#x3C;/name>
    &#x3C;summary>Creates a new Project&#x3C;/summary>

    &#x3C;when-to-use>
      Call this tool if the user either explicitly asks you to create a new project, or if it is clear from the context that the user would like you to do so. For example, if the user says "I started a new project called Project Orion today, so far I got the website skeleton in place and basic auth too", you should create a new project called Project Orion, before calling extractAchievements
    &#x3C;/when-to-use>
  &#x3C;/Tool>
&#x3C;/Tools>

Here are some examples of messages from the user and the tool selection or response you should make:

&#x3C;Examples>
  &#x3C;Example>
    User: I fixed up the bugs with the autofocus dashboard generation and we launched autofocus version 2.1 this morning.
    Router LLM: Call extractAchievements tool
  &#x3C;/Example>
  &#x3C;Example>
    User: Write a weekly report for my work on Project X for the last 7 days.
    Router LLM: Call createDocument tool, with the days set to 7, and the correct projectId and companyId
  &#x3C;/Example>
  &#x3C;Example>
    User: I started a new project called Project Orion today, so far I got the website skeleton in place and basic auth too. Please create a new project called Project Orion and call extractAchievements
    Router LLM: Call createProject tool, and then call extractAchievements tool
  &#x3C;/Example>
&#x3C;/Examples>

Here now are the actual data for you to consider:

&#x3C;Data>
  &#x3C;ChatHistory messages={data.chatHistory} />
  &#x3C;today>{new Date().toLocaleDateString()}&#x3C;/today>
  &#x3C;Companies companies={data.companies} />
  &#x3C;Projects projects={data.projects} />
  &#x3C;UserInput>User message: {data.message}&#x3C;/UserInput>
&#x3C;/Data>

Your response:
</code></pre>
<p>This prompt will be run against a given chat session between a user and the LLM Router. This gets rendered in the <code>&#x3C;ChatHistory /></code> component, with the latest message being emphasized in the <code>&#x3C;UserInput /></code> component;</p>
<p>Based on the user message and the chat history, the LLM is asked to select a tool to use, or to reply directly to the user. The way it's implemented in bragdoc, some of those tool calls are actually the secondary LLMs that are invoked in the LLM Router pattern.</p>
<p>The generate document tool is one such example - the user posts a message to the chat conversation, which then invokes a tool which spins up an inner LLM prompt that generates the document without spending much of the outer LLM's context on the effort, and getting a better outcome at the same time.</p>
<h2>How the Prompt Gets Called</h2>
<p>As you can tell from the lengthy prompt above, this prompt expects quite a bit of data to be passed in. It wants the recent chat history, including the current message, plus the user's companies and projects. In a <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">recent post on mdx-prompt</a> I described the fetch/render/execute cycle that I tend to apply for almost all of my LLM calls. Those are:</p>
<ul>
<li><strong>fetch</strong>: given a minimal amount of data, load the data required to render the prompt</li>
<li><strong>render</strong>: given the data loaded in the fetch step, render the prompt to the UI</li>
<li><strong>execute</strong>: given the rendered prompt, run it and return the result</li>
</ul>
<p>The <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/llm-router.ts">llm-router.ts</a> file in <a href="https://github.com/edspencer/bragdoc-ai">bragdoc-ai</a> implements this pattern for the main Bragdoc LLM Router, and it's a good example of how to use mdx-prompt with NextJS. That's a 400 line file, though most of that is tool definitions.</p>
<p>Let's take a look at our 3 functions, starting with fetch:</p>
<pre><code class="language-llm-router.ts">export async function fetch(
  props: LlmRouterFetchProps
): Promise&#x3C;LlmRouterPromptProps> {
  const { user, chatHistory, message, onEvent } = props;

  const [companies, projects] = await Promise.all([
    getCompaniesByUserId({ userId: user.id }),
    getProjectsByUserId(user.id),
  ]);

  return {
    user,
    companies,
    projects,
    chatHistory,
    message,
    onEvent,
  };
}
</code></pre>
<p>Pretty trivial. We do this so that we can call the LLM Router from many different parts of our app without having to re-implement the loading of companies and projects each time we do so.</p>
<p>Next up, let's look at render:</p>
<pre><code class="language-llm-router.ts">const promptPath = path.resolve('./lib/ai/prompts/llm-router.mdx');

/**
 * Renders the LLM router prompt using the provided data.
 *
 * @param {LlmRouterPromptProps} data - The data including user details, companies, projects, etc
 * @returns {Promise&#x3C;string>} The rendered prompt.
 */
export async function render(data: LlmRouterPromptProps) {
  return await renderMDXPromptFile({
    filePath: promptPath,
    data,
    components,
  });
}
</code></pre>
<p>Equally trivial, it's just passing the data from <code>fetch</code> into mdx-prompt for rendering.</p>
<p>The big boy is the <code>execute</code> function, chiefly because it currently inlines all of the tools (which is not great architecture as the tools are not easily testable that way, but that's a topic for another day). All this does is send our rendered prompt plus 3 tools (createDocument, updateDocument, and extractAchievements) to the LLM, and stream the results back to the user.</p>
<p>All 3 of those tool calls are in fact secondary LLM calls - they're also mdx-prompt prompts, and follow the same fetch/render/execute cycle as the LLM Router does. And that's all an LLM Router really is - just an LLM something that dispatches queries to other LLMs.</p>
<pre><code class="language-llm-router.ts">/**
 * Executes the LLM router with the provided prompt and data.
 *
 * @param {LlmRouterExecuteProps} props - The properties including prompt, stream text options, etc
 * @returns {Promise&#x3C;JSONValue>} The result of the execution.
 */
export function execute({
  prompt,
  streamTextOptions,
  data,
  onEvent,
  tools,
}: LlmRouterExecuteProps) {
  const eventCallback = data.onEvent || onEvent;

  //This is a tool of the LLM Router, but it's a tool that calls another LLM prompt
  const createDocument = async ({
    title,
    days,
    projectId,
    companyId,
  }: CreateDocumentExecuteProps) => {
    const { user, chatHistory, message } = data;

    const id = generateUUID();
    let draftText = '';

    eventCallback?.({type: 'id', content: id,});
    eventCallback?.({type: 'title', content: title,});
    eventCallback?.({type: 'clear', content: '',});

    //this is where we dispatch to another LLM call - generateDocument is itself an mdx-prompt prompt
    const { fullStream } = await generateDocument({
      user,
      projectId: projectId ?? undefined,
      companyId: companyId ?? undefined,
      title,
      days,
      chatHistory,
    });

    for await (const delta of fullStream) {
      const { type } = delta;

      if (type === 'text-delta') {
        const { textDelta } = delta;
        draftText += textDelta;
        eventCallback?.({
          type: 'text-delta',
          content: textDelta,
        });
      }
    }

    eventCallback?.({type: 'finish', content: '',});

    if (user.id) {
      await saveDocument({
        id,
        title,
        content: draftText,
        userId: user.id,
      });
    }

    return {
      id,
      title,
      content: 'A document was created and is now visible to the user.',
    };
  };


  const updateDocument = async ({id, description,}: { id: string; description: string; }) => {
    //... truncated for brevity
  };

  const extractAchievements = async () => {
    //... truncated for brevity
  };

  return streamText({
    prompt,
    maxSteps: 10,
    ...streamTextOptions,
    model: routerModel,
    tools: {
      extractAchievements: {
        description:
          'Extract achievements from the chat to be saved to the database',
        parameters: z.object({}),
        execute: tools?.extractAchievements || extractAchievements,
      },

      createDocument: {
        description: "Create a document based on the User's achievements",
        parameters: z.object({
          title: z.string().describe('The title of the document'),
          days: z
            .number()
            .int()
            .min(1)
            .max(720)
            .describe('The number of days ago to load Achievements from'),
          projectId: z
            .string()
            .optional()
            .describe('The ID of the project that the user is talking about'),
          companyId: z
            .string()
            .optional()
            .describe(
              "The ID of the company that the user is talking about (use the project's company if not specified and the project has a companyId)"
            ),
        }),
        execute: tools?.createDocument || createDocument,
      },
      updateDocument: {
        description: 'Update a document with the given description',
        parameters: z.object({
          id: z.string().describe('The ID of the document to update'),
          description: z
            .string()
            .describe('The description of changes that need to be made'),
        }),
        execute: tools?.updateDocument || updateDocument,
      },
    },
  });
}
</code></pre>
<p>That's about a hundred lines of code but it's pretty easy stuff. And that's really all there is to it.</p>
<h2>Integrating into NextJS</h2>
<p>This is also fairly straightforward. I use the excellent Vercel AI SDK for all of my work with LLMs, and this one is no exception. The actual source code of the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/app/api/chat/route.ts">api/chat/route.ts is here</a>, but here's a slightly slimmed down version:</p>
<pre><code class="language-route.ts">import { streamFetchRenderExecute } from "@/lib/ai/llm-router";

export const maxDuration = 120;

export async function POST(request: Request) {
  // truncated all other the auth and data loading code. All we need is userMessage

  const userMessage = getMostRecentUserMessage(coreMessages);

  const streamingData = new StreamData();

  streamingData.append({
    type: "user-message-id",
    content: userMessageId,
  });

  const result = await streamFetchRenderExecute({
    input: {
      user: session.user as User,
      chatHistory: coreMessages,
      message: userMessage.content as string,
    },
    //onEvent is our own custom callback, implemented in streamFetchRenderExecute,
    //where each message is JSON suitable to be streamed back to the client
    onEvent: (item: any) => {
      streamingData.append(item);
    },
    streamTextOptions: {
      onFinish: async ({ response }) => {
        try {
          //save messages to the database
          await saveMessages({... truncated for brevity ... });
        } catch (error) {
          console.error("Failed to save chat");
          console.log(error);
        }

        streamingData.close();
      },
      experimental_telemetry: {
        isEnabled: true,
        functionId: "stream-text",
      },
    },
  });

  return result.toDataStreamResponse({
    data: streamingData,
  });
}
</code></pre>
<p>That's really all it is. We have a simple function called <code>streamFetchRenderExecute</code>, which just calls our <code>fetch</code>, <code>render</code> and <code>execute</code> functions in order, returning us what the <code>execute</code> function returns, which is just the Vercel AI SDK's <a href="https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text">streamText</a> function.</p>
<p>In the UI, we use <a href="https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat">useChat</a> inside our <a href="https://github.com/edspencer/bragdoc-ai/blob/main/components/chat.tsx#L50">chat.tsx component</a>, which works out of the box with streamText, so at this point we're pretty much done. If the LLM wants to respond in text, that gets streamed back to the UI in the normal way, and if it needs instead to dispatch to another LLM call, that all happens transparently under the covers, with the <code>StreamData</code> object used to stream tool call progress/results back too.</p>
<h2>There Can Be (more than) Only One</h2>
<p>You don't need to limit yourself to a single LLM Router. In fact, it's often a good idea to have more than one. <a href="https://www.taskdemon.ai">Task Demon</a> is an order of magnitude more sophisticated than Bragdoc and currently uses no fewer than 6 different LLM Routers - one for each of the different Agents in the system. For now this article is long enough already, but I'll be writing about that in the future.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/llm-router.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[claudify: fire and forget for Claude Code]]></title>
            <link>https://edspencer.net//2025/5/14/claudify-fire-forget-claude-code</link>
            <guid>claudify-fire-forget-claude-code</guid>
            <pubDate>Wed, 14 May 2025 09:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sometimes I find myself doing the same thing over and over again. One of those things looked like this:</p>
<ol>
<li>Find that my test suite is failing</li>
<li>Open up Claude Code</li>
<li>"Please run <code>pnpm test</code> and fix the failures"</li>
<li>Wait</li>
</ol>
<p>Maybe there's only one failing test out of the ~1000 tests in the suite, so we can kinda optimize it a little:</p>
<ol>
<li>Look to see which test file was causing the problem</li>
<li><code>pnpm test /path/to/that/file.test.ts</code></li>
<li>Open up Claude Code</li>
<li>Copy/paste the <code>pnpm test ...</code> command and its output and hit enter</li>
<li>Wait</li>
</ol>
<p>That's faster as it lets Claude Code focus on a single test file. But it still involved me doing the work of copying and pasting the command and its output. It's a First World Problem of truly quotidian proportions.</p>
<p>What if I could do this instead, and have it be equivalent to all the hard work described above?:</p>
<pre><code class="language-bash">$ pnpm test /path/to/that/file.test.ts
$ claudify
</code></pre>
<p>Turns out that's possible with a fairly simple little shell script. I call it <a href="https://github.com/edspencer/claudify">claudify</a>, though I usually alias it to just <code>'fix'</code>.</p>
<h2>How does it work?</h2>
<p>It's pretty basic. The <a href="https://github.com/edspencer/claudify/blob/main/claudify.sh">entire script</a> is only 70 lines long and most of that is documentation and logging. Here's the core of it:</p>
<ol>
<li>Pulls the most recent command off your shell history (using <code>fc -ln -1</code>)</li>
<li>Runs that command again, captures the output</li>
<li>Throws the command and its output into a tiny prompt</li>
<li>Sends that prompt to Claude Code</li>
<li>Prints a subset of the Claude Code output as it works</li>
</ol>
<p>That's it. It's a pretty dumb and simple script that wraps Claude Code and takes advantage of the <code>-p</code> and <code>--output-format</code> <a href="https://docs.anthropic.com/en/docs/claude-code/cli-usage">flags supported by Claude Code</a>.</p>
<h2>How do you install it?</h2>
<p><code>claudify</code> needs to be a function in your shell, so you can add it to your <code>.bashrc</code> or <code>.zshrc</code> file. My shell-fu is not good enough to make it work via a bash script (because executing a bash script seems to generate a new history stack).</p>
<p>So the answer is to just copy/paste the function into your <code>.zshrc</code>, <code>.bashrc</code> or similar. Make sure you run <code>source ~/.zshrc</code> or <code>source ~/.bashrc</code> to make it take effect.</p>
<p><a href="https://github.com/edspencer/claudify/blob/main/claudify.sh">Grab the file here</a>, or try <a href="https://github.com/edspencer/claudify/blob/main/claudify.ps1">this one</a> if you're on Windows (no promises on the Windows one as the only thing I use my PC for is playing Doom).</p>
<h2>Customizing with reusable instructions</h2>
<p>I find myself using this in two main contexts:</p>
<ol>
<li>Fixing a broken Next JS build</li>
<li>Fixing failed tests</li>
</ol>
<p>Claude Code really does not want to write tests the way I want them. If I had a dollar for every time I told it "don't mock the database, insert real data and clean it up afterwards", I'd be a good deal happier in life.</p>
<p><code>claudify</code> accepts a single parameter that allows you to customize the prompt that gets passed in to Claude Code along with the most recent command and its output (the default prompt is just "Please fix this:"). I tend to just create some simple shell aliases with different instructions based on what I want.</p>
<p>These 2 aliases are the most useful to me:</p>
<pre><code class="language-bash">alias fix="claudify"
alias fixtest="claudify \"Please fix this test. Do not mock the database, \
  instead insert whatever data are required in a beforeEach and delete it again \
  surgically in an afterEach. Do not generate your own UUIDs, \
  let the database do that and read the IDs if you need them.\""
</code></pre>
<p>So now in my day-to-day, if <code>pnpm build</code> fails, I tend to just run <code>fix</code> and go do something else for a while until it's fixed all the TS issues the most recent vibe coding session introduced. Similarly, if a test fails I'll run <code>fixtest</code> and let it do its thing.</p>
<p>It probably saves me 30 seconds each time I do it. It won't change your life, but it might be mildly convenient once in a while.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/claudify.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing Task Demon: Vibe Coding with a Plan]]></title>
            <link>https://edspencer.net//2025/4/14/task-demon-vibe-coding-with-a-plan</link>
            <guid>task-demon-vibe-coding-with-a-plan</guid>
            <pubDate>Mon, 14 Apr 2025 08:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>In the last 6 months, the way that leading software engineers build software has undergone a fundamental shift.</p>
<p>The adoption of agentic AI coding assistants has heralded the greatest leap in productivity I have encountered in my 20 year career so far. As I wrote previously, <a href="/2025/1/8/how-i-built-bragdoc">adopting Windsurf doubled my output within a week</a>. Where usually I'd be thrilled to find some way to get 20% more done, and would work hard for that 20%, suddenly I'm getting 100% and it's just... easy.</p>
<p>But if there's a single consistent counter-punch to the Vibe Coding movement, it's the irrefutable fact that no matter how good the agentic AI coding assistant is, it will always do much better work from a detailed prompt that includes a plan, than from your 2 sentence vibe code prompt.</p>
<p>That's what <a href="https://www.taskdemon.ai">Task Demon</a> does: it takes the 2 sentence vibe prompt and blows it up into a sublimely detailed prompt, usually anywhere between 200 and 1000 lines long, that includes a full implementation plan that will correctly guide the AI to do the right thing, using your project's structure, dependencies and ways of doing things.</p>
<p>A video is worth a million words. This one is 15 minutes but if you use AI to build software, <a href="https://www.youtube.com/watch?v=tmC724XcE5Q">I believe you'll find it worth it</a>:</p>
<h2>How it works</h2>
<p>After using Windsurf and later Claude Code for a while, I found that using the following pattern yielded superb results:</p>
<ol>
<li>Ask the AI to write a plan on how to do a certain Task</li>
<li>Ask it to check that plan as it usually makes mistakes</li>
<li>Ask it to write the code based on that plan</li>
</ol>
<p>I developed these detailed prompts for each step that I would reuse each time and just swap out the task description. The rest of the prompt text was always stuff about what dependencies to use, how to run tests, and so on. I'd copy the output of each prompt in as the input of the next, until I'd run all 3 prompts and the code had in theory been implemented.</p>
<p>After doing this for a while, it was working very successfully, but I was no longer doing engineering. I was doing manufacturing - copy/pasting text snippets between prompts over and over again in the same way every time, just swapping out a few key sentences and making quite a few mistakes along the way.</p>
<p><a href="https://www.taskdemon.ai">Task Demon</a> automates this process. When you create a task with Task Demon, it uses an agentic planning process to generate an extremely detailed prompt that includes detailed background on the project and the task, a full pre-planned implementation plan and any custom instructions you want to add. Just paste the resulting 200-1000 line prompt into Windsurf, Claude Code, or any other agentic AI coding assistant, and let it do the rest.</p>
<h2>Planning beats Vibes</h2>
<p>Because there are often many ways to do the same thing, the AI will often choose the wrong way to do it for your project. Then you have to prompt it again, pleading with it to do it right this time. Often there are several rounds of this. And to be fair, the AI had no idea that your project already uses Convention X and Library Y - your vibe code prompt definitely didn't tell it.</p>
<p>This is where having a pre-authored plan prompt massively accelerates software development vs Vibe Coding. Task Demon's prompts are incredibly detailed and completely tailored to the specific project that you are working on so when you paste them into Claude Code and press enter, generally speaking you don't have to intervene very much because the prompt already tells Claude Code everything it needs to do the job properly.</p>
<p>Here's an example of a prompt that <a href="https://www.taskdemon.ai">Task Demon</a> generated:</p>
<p>This was generated from a 2-sentence description I typed in to one of the <a href="https://www.taskdemon.ai">Task Demon</a> chatbot UIs. This prompt is 1035 lines long, which is on the longer side of what Task Demon will normally generate, but scrolling through it you can see why any agentic AI coding assistant would have a far better chance implementing the right thing given this, than given a 2 sentence hit-enter-and-hope vibe prompt.</p>
<p>A theme of what <a href="https://www.taskdemon.ai">Task Demon</a> does is to try to pull as much computation forward as possible. This is why the Characterization Agent is so important - by spending 10-15 minutes understanding your project in detail one time, we avoid having to learn it all again each time we need to generate a prompt to do some work.</p>
<p>So how does this all work? Characterization, Claude Code and <a href="https://www.taskdemon.ai">Task Demon</a> itself:</p>
<h2>Characterization &#x26; Claude Code</h2>
<p>Characterization is the way that Task Demon gets to know your project. It is a conversation between 2 agentic AIs - one runs in Task Demon's cloud servers, the other is Claude Code, running in your local environment. Mediating this conversation is a simple NPM module called the Task Demon CLI agent, which just acts as a conduit to allow Task Demon to drive Claude Code.</p>
<p>The Task Demon Characterization Agent starts the process by asking Claude Code for a detailed technical document describing your project at a high level - what languages it uses, whether it appears to be of a well-known framework layout, what dependencies it has, what the database schema looks like, and so on.</p>
<p>When it gets it back (via the Task Demon CLI Agent), the Characterization Agent analyzes this document and will either decide that the project is simple enough to be characterized by this document alone, or it will ask Claude Code for up to half a dozen followup documents on subjects like data schema, API endpoints, business logic, etc.</p>
<p>When the Characterization Agent is satisfied that it has learned enough about your project to be able to make competent plan preparation prompts, it declares the process done and then uses the Characterization as the basis for all of the Task processing it does, from triage to plan generation to task creation to implementation prompt generation.</p>
<p>Task Demon is designed to avoid having any direct contact with any of your code or data. Task Demon itself delegates all of its interactions with your code to Claude Code, which you need to have installed in your local environment.</p>
<h2>Task Demon</h2>
<p><a href="https://www.taskdemon.ai">Task Demon</a> is an agentic AI SaaS application that upgrades your simple ticket descriptions into highly effective LLM prompts that help you Vibe Code like a grownup. Using Task Demon to drive Claude Code has more than doubled my output; often it seems more like a 10x increase.</p>
<p>Claude Code by itself is already an immensely powerful tool for software engineering, but it's also a bottleneck in that it can only really do one thing at a time. With an excellent prompt containing a detailed plan, Claude Code can usually complete the task much faster than with a vibe prompt, and usually with minimal human intervention.</p>
<p>This stuff hasn't suddenly become magic - the reason Task Demon gives you a "Copy Prompt" button is that it's still expecting you to keep an eye on Claude Code (or whichever agent you use) as it does its thing. Task Demon does have a "YOLO Mode", but that's a topic for another day... for now Task Demon gets you much closer to a one-shot prompt for most tasks, but you are still going to want to refine or tweak things pretty often during its implementation.</p>
<p><a href="https://www.taskdemon.ai">Task Demon</a> is a product that I built by myself in the last 8 weeks. There's no Venture Capital funding behind it, and I'm a team of one. It costs money to run Task Demon, though I'm able to offer a limited number of free trials per month - see details at https://www.taskdemon.ai/trial for how many slots are still open right now.</p>
<h2>Individual Mode Available Now; Team Plans Coming Soon</h2>
<p>Today's release of Task Demon makes the Individual mode available to everyone, and I'm working on the Team mode now - expect it to be available in the next few weeks.</p>
<p>In the meantime I'm generally releasing updates multiple times per day, which you can see (as well as come get support) in the <a href="https://discord.com/invite/y3vqbV2P">Task Demon Discord Server</a>.</p>
<p>I write Task Demon by myself, and there's zero dollars of Venture Capital or other funding behind me. It's a paid product aimed at professionals who write code or otherwise contribute to software getting written. It has more than doubled my output, and its ROI measured against its cost is in the tens of thousands of percent. If you can't get at least a 10% productivity boost while using it, email me at support@taskdemon.ai and I'll a) try to make it work better for you and b) refund your money, no questions asked.</p>
<h2>Try it out!</h2>
<p>You can try out Task Demon for free at https://www.taskdemon.ai/trial - there's a limit of 100 free trials per month, but they reset every month.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/task-demon-logo-wide.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Deep Research Yourself]]></title>
            <link>https://edspencer.net//2025/2/11/deep-research-yourself</link>
            <guid>deep-research-yourself</guid>
            <pubDate>Tue, 11 Feb 2025 08:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>After 2 years of doing my own thing, I recently got the itch to work on something bigger than myself again and earn some money in the process. After talking to a few interesting companies, I was reminded that hiring engineers is really hard, really time consuming and has a large degree of risk attached to it.</p>
<p>When I think about which company makes the most sense for me to join, I picture myself as a jigsaw piece, with a unique blend of skills, experience and personality traits that you could conceivably draw as a pretty complex jigsaw piece. Each company is also a jigsaw, with a bunch of pieces missing. Just as your shape is unlike anyone elses, so each company's gaps are uniquely shaped as well.</p>
<p>As I plan to do full stack engineering for a company that has a strong AI focus, the jigsaw for a company that might be an optimal fit for me could look like this. Each blue piece is a position the company has already filled, with the blank ones being empty positions they are hiring for:</p>
<p>Imagining myself as the green piece and other candidates for the role as the orange and red, this is a company jigsaw where I would have high alignment, because the shape of my puzzle piece fits with the gap in the company jigsaw without missing areas or overlapping too much.</p>
<p>This is a good company to consider joining, with both company and candidate benefitting from the strong alignment. Our orange and red candidates don't fit so well, or overlap too much, so their ability to create value for the company (and therefore themselves) is lower.</p>
<h2>When people research you, what do they see?</h2>
<p>Thinking from the hiring company's point of view, it's quite a lot of effort to do the research on a candidate. I honestly don't know if the automated candidate screening tooling is good enough to trust yet, but there are 2 things I do know:</p>
<ol>
<li>Almost all the information they will gather about you will be from the internet</li>
<li>You don't get to see a copy of what they find out about you</li>
</ol>
<p>With <a href="https://openai.com/index/introducing-deep-research/">OpenAI's release of Deep Research last week</a>, it starts to be possible for candidates to do some of the same kinds of research on themselves. Deep Research is an ideal way to do something like this, for a few reasons:</p>
<ul>
<li>Critically examining yourself is both uncomfortable and subject to bias</li>
<li>You already know yourself pretty well, but the person on the other side only has google to go on</li>
<li>It's kinda boring and time-consuming, but the AI doesn't care</li>
</ul>
<h2>Deep Research Yourself</h2>
<p>With access to a tool like Deep Research, it's pretty easy to have it go off and perform that dispassionate research on you and give you a candid assessment of what companies see when they think about hiring you.</p>
<p>I went ahead and did that on myself and made the response public (link below). I put myself in the shoes of a hiring manager for the type of company jigsaw I imagine myself fitting into, and asked it the following:</p>
<pre><code class="language-md">I am considering hiring a full stack software engineer called Ed Spencer.
He has a blog somewhere and says he is good at full stack engineering.
We're using React with Next JS and have a big complex AI model on the backend 
but need someone to bring order to the front end and understand the back end 
enough to make sure the whole thing works well, is fast and delightful to users, 
things like that.

Please research this guy and give me a full report on everything you could find 
about him and whether he would be a suitable candidate for the position.
</code></pre>
<p>This is the sort of requirement I hear all the time when people are looking for someone who does the type of thing I do. ChatGPT had me answer a follow-up question, as it is wont to do, and then it went off and browsed the web for 5 minutes before coming back with a report:</p>
<p>In the end it churned out <a href="https://chatgpt.com/share/67a64ef9-6bc4-800e-a6c3-0e21c9b0386e">this final report</a>. I published it because a) it's trivial for anyone with ChatGPT Pro to copy and paste the prompt above and reproduce it and b) so you can see an example of what it comes up with:</p>
<p>You can read <a href="https://chatgpt.com/share/67a64ef9-6bc4-800e-a6c3-0e21c9b0386e">the full report here</a> if you want to see what the rest looks like. Employers are going to be increasingly relying on techniques and technologies like this to find the right candidates, so it's important to make sure that what your ideal company sees matches when you want them to see. The first step to doing that is to do the deep research on yourself.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/puzzle-pieces.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Eval-Driven Design with NextJS and mdx-prompt]]></title>
            <link>https://edspencer.net//2025/2/3/edd-eval-driven-design-mdx-prompt</link>
            <guid>edd-eval-driven-design-mdx-prompt</guid>
            <pubDate>Mon, 03 Feb 2025 08:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>In the previous article, we went on a deep dive into <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">how I use mdx-prompt on bragdoc.ai</a> to write clean, <a href="https://github.com/edspencer/mdx-prompt">composable LLM prompts</a> using good old JSX. In that article as well as the <a href="/2025/2/3/mdx-prompt-composable-prompts-with-jsx">mdx-prompt announcement article</a>, I promised to talk about Evals and their role in helping you architect and build AI apps that you can actually prove work.</p>
<p>Evals are to LLMs what unit tests are to deterministic code. They are an automated measure of the degree to which your code functions correctly. Unit tests are generally pretty easy to reason about, but LLMs are usually deployed to do non-deterministic and somewhat fuzzy things. How do we test functionality like that?</p>
<p>In <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">the last article</a> we looked at the extract-achievements.ts file from <a href="https://www.bragdoc.ai">bragdoc.ai</a>, which is responsible for extracting structured work achievement data using well-crafted LLM prompts. Here's a reminder of what that Achievement extract process looks like, with its functions to fetch, render and execute the LLM prompts.</p>
<p>When it comes right down to it, when we say we want to test this LLM integration, what we're trying to test is render() plus execute(), or our convenience function <code>renderExecute</code>. This allows us to craft our own ExtractAchievementsPromptProps and validate that we get reasonable-looking ExtractedAchievement objects back.</p>
<p>ExtractAchievementsPromptProps is just a TS interface that describes all the data we need to render the LLM prompt to extract achievements from a chat session. It looks like this:</p>
<pre><code class="language-types.ts">//props required to render the Extract Achievements Prompt
export interface ExtractAchievementsPromptProps {
  companies: Company[];
  projects: Project[];
  message: string;
  chatHistory: Message[];
  user: User;
}
</code></pre>
<p>ExtractedAchievement is equally basic - just a subset of our <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/db/schema.ts#L116">Achievement type</a> (which is itself just a drizzle model).</p>
<pre><code class="language-types.ts">// the type of Achievement emitted by the LLM wrapper (not saved to db yet)
// basically what the LLM sent back plus a couple of fields like impactUpdatedAt
export type ExtractedAchievement = Pick&#x3C;
  Achievement,
  | 'title'
  | 'summary'
  | 'details'
  | 'eventDuration'
  | 'eventStart'
  | 'eventEnd'
  | 'companyId'
  | 'projectId'
  | 'impact'
  | 'impactSource'
  | 'impactUpdatedAt'
>;
</code></pre>
<p>Our execute function - the function that actually runs a rendered LLM prompt - is an async generator that yields <code>ExtractedAchievement</code> objects. Our Eval will need to collect those <code>ExtractedAchievement</code> objects and compare them to the Achievements it expects to see.</p>
<pre><code class="language-extract-achievements.tsx">/**
 * Executes the rendered prompt and yields the extracted achievements
 * 
 * @param prompt string
 * @returns AsyncGenerator&#x3C;ExtractedAchievement, void, unknown>
 */
export async function* execute(prompt: string): AsyncGenerator&#x3C;ExtractedAchievement, void, unknown> {
  const { elementStream } = streamObject({
    model: extractAchievementsModel,
    prompt,
    temperature: 0,
    output: 'array',
    schema: achievementResponseSchema,
  });

  for await (const element of elementStream) {
    yield {
      ...element,
      summary: element.summary || '',
      details: element.details || '',
      eventStart: element.eventStart ? new Date(element.eventStart) : null,
      eventEnd: element.eventEnd ? new Date(element.eventEnd) : null,
      impactSource: 'llm',
      impactUpdatedAt: new Date(),
    };
  }
}
</code></pre>
<h2>Crafting a Scenario</h2>
<p>So now that we're familiar with the shapes of the data at the start and the end of the Eval process, we can start to put together a scenario to test our LLM code. This is probably the hardest part - there are infinitely many approaches and you probably need more than one of them.</p>
<p>But one's better than none so let's imagine a scenario for a user who is an engineer, has a couple of companies and projects, and tells the bragdoc AI a couple of things that they've been working on. First up we'll need some data to represent this user, their companies and projects. Here's a snippet of some fake data that we can use to represent this user:</p>
<pre><code class="language-data/user.ts">import type { User } from '@/lib/db/schema';
import { v4 as uuidv4 } from 'uuid';

export const user: User = {
  name: 'Ed Spencer',
  preferences: {
    documentInstructions: `If I don't mention a specific project, I'm talking about Brag Doc.`,
    language: 'en',
    hasSeenWelcome: true
  },
  id: uuidv4(),
  email: 'Q3Sd2@example.com',
} as User;

export const previousCompany = {
  name: 'Palo Alto Networks',
  id: uuidv4(),
  startDate: new Date('2016-02-01'),
  endDate: new Date('2021-09-30'),
  userId: user.id,
  role: 'Principal Engineer',
  domain: 'www.paloaltonetworks.com',
};

export const company = {
  name: 'Egghead Research',
  id: uuidv4(),
  startDate: new Date('2023-01-01'),
  endDate: null,
  userId: user.id,
  role: 'Chief Scientist',
  domain: 'www.edspencer.net',
};

export const project1 = {
  name: 'BragDoc.ai',
  description: 'AI-powered self-advocacy tool for tech-savvy individuals.',
  startDate: new Date('2024-12-15'),
  endDate: null,
  id: uuidv4(),
  companyId: company.id,
  status: 'active',
  userId: user.id,
  repoRemoteUrl: null
}

export const project2 = {
  name: 'mdx-prompt',
  description: 'Composable LLM prompts with JSX and MDX',
  startDate: new Date('2023-01-01'),
  endDate: new Date('2023-06-30'),
  id: uuidv4(),
  companyId: company.id,
  status: 'active',
  userId: user.id,
  repoRemoteUrl: null
}

export const projects = [project1, project2];
export const companies = [company, previousCompany];
</code></pre>
<h2>Defining Experiments</h2>
<p>I'm using <a href="https://braintrust.dev">Braintrust</a> to run and track the Evals for <a href="https://www.bragdoc.ai">bragdoc.ai</a>, but most of this article really applies to any way of running them. You don't need to be using Braintrust specifically.</p>
<p>One thing they do formalize, though, is the idea of an Experiment. An Experiment is just a type that represents some input and some expected output. And here's where our careful thinking and structuring of our fetch/render/execute architecture pays off - we can just use our ExtractAchievementsPromptProps and ExtractedAchievement types to define our Experiment type:</p>
<pre><code class="language-extract-achievements.eval.ts">export type Experiment = {
  input: ExtractAchievementsPromptProps;
  expected: ExtractedAchievement[];
};
</code></pre>
<p>This makes total sense. We're skipping the fetch stage as we're providing our own fake/controlled data set, so we're testing from ExtractAchievementsPromptProps to ExtractedAchievement[] on our diagram above.</p>
<h2>Writing the Eval</h2>
<p>Braintrust will help us run an array of these Experiments - here's how we structure this particular experiment, which is just a single chat message from the user:</p>
<pre><code class="language-extract-achievements.eval.ts">const chatHistory = [
  {
    role: 'user' as const,
    content: 'I fixed several UX bugs in the checkout flow on Bragdoc today',
    id: '1',
  },
];

const lastMidnight = new Date();
lastMidnight.setHours(0, 0, 0, 0);

const nextMidnight = new Date();
nextMidnight.setDate(nextMidnight.getDate() + 1);
nextMidnight.setHours(0, 0, 0, 0);

const experimentData: Experiment[] = [  
  {
    input: {
      companies,
      projects,
      chatHistory,
      user,
      message: 'I fixed several UX bugs in the checkout flow on Bragdoc today',
    },
    expected: [
      {
        summary: 'Fixed several UX bugs in the checkout flow',
        details: 'Fixed several UX bugs in the checkout flow on Bragdoc',
        eventStart: lastMidnight,
        eventEnd: nextMidnight,
        impactSource: 'llm',
        impactUpdatedAt: new Date(),
        companyId: companies[0].id,
        projectId: projects[0].id,
        title: 'Fixed several UX bugs in the checkout flow',
        eventDuration: 'day',
        impact: 1,
      },
    ],
  },
];
</code></pre>
<p>We can see that the <code>input</code> is an instance of our <code>ExtractAchievementsPromptProps</code> type, and the <code>expected</code> is an array of ExtractedAchievement objects that we expect to be yielded by our execute() function. There is a third concept - <code>output</code> - which is the actual array of <code>ExtractedAchievement</code> objects that we get back from the LLM.</p>
<p>So in the end this eval is just testing that when the user sends a message in an otherwise empty chat session, saying "I fixed several UX bugs in the checkout flow on Bragdoc today", we get back the <code>output</code> of <code>ExtractedAchievement</code> objects that looks like what we <code>expected</code>, given the company/project/user data that we also fed the prompt with the rest of the <code>input</code>.</p>
<h2>Fuzzy matching and LLMs as judges</h2>
<p>Ok so we've got a good handle on what we're testing, and we've got some good test data to test it with. But how do we actually compare the ExtractedAchievement objects that we get back from the LLM with the <code>ExtractedAchievement</code> objects that we expect?</p>
<p>This bit is where it can be challenging, because there are arguably many different reasonable ways an LLM could respond to a given message like this. In other Experiments along the same lines, we want to be able to pass in very long messages and have a bunch of Achievements extracted from them. Some of the other Evals for bragdoc.ai do just that - setting up scenarios where we expect a dozen or more Achievements to be extracted from a single LLM invocation.</p>
<p>In this case we're essentially comparing 2 arrays of identically-shaped JSON objects, but there's still enough fuzziness in the LLM output that we can't just do a deep comparison of the arrays. We certainly can't expect to be able to run a full comparison of the 2 arrays because there is a lot of non-determinism in the LLM output.</p>
<p>So we need to be a bit more creative. We can use the LLM as a judge, and ask it to compare the <code>output</code> and <code>expected</code> arrays for us. We can do this by asking another LLM to compare each <code>ExtractedAchievement</code> object in the <code>output</code> array with each <code>ExtractedAchievement</code> object in the <code>expected</code> array, and give us a score for how similar they are.</p>
<p>This general technique is called "LLM as a Judge", and it's a powerful way to compare fuzzy data. We can use it to compare the <code>output</code> and <code>expected</code> arrays, and then use that comparison to decide whether the Eval passed or failed. Conveniently, we can use <a href="https://github.com/edspencer/mdx-prompt">mdx-prompt</a> to write the prompt that does this comparison for us:</p>
<pre><code class="language-extract-achievement-scorer.tsx">
//snipped for brevity, contains instructions on how to compare the output and expected arrays
const instructions = ['...']

//tell the LLM how to score the comparison
const outputFormat = `
Answer by selecting one of the following options:
(A) The extraction matches the expected output perfectly
(B) The extraction captures the main achievement but misses some details
(C) The extraction has minor inaccuracies but is generally correct
(D) The extraction misses key information or has significant inaccuracies
(E) The extraction is completely incorrect or misunderstands the achievement`;

function EvaluateExtractedAchievementsPrompt({
  expectedAchievements,
  extractedAchievements,
}: {
  expectedAchievements: any;
  extractedAchievements: any;
}) {
  return (
    &#x3C;Prompt>
      &#x3C;Purpose>
        You are evaluating how well an AI system extracted achievements from a
        user message. Compare the extracted achievements with the expected
        output. Consider that a single message may contain multiple
        achievements. Return one of the scores defined below.
      &#x3C;/Purpose>
      &#x3C;Instructions instructions={instructions} />
      &#x3C;InputFormat>
        &#x3C;expected-achievements>
          The correct Achievements that should have been extracted by the model
        &#x3C;/expected-achievements>
        &#x3C;extracted-achievements>
          The achievements that were actually extracted by the model
        &#x3C;/extracted-achievements>
      &#x3C;/InputFormat>
      &#x3C;OutputFormat format={outputFormat} />
      &#x3C;Variables>
        &#x3C;expected-achievements>
          {JSON.stringify(expectedAchievementsPlucked, null, 4)}
        &#x3C;/expected-achievements>
        &#x3C;extracted-achievements>
          {JSON.stringify(extractedAchievementsPlucked, null, 4)}
        &#x3C;/extracted-achievements>
      &#x3C;/Variables>
    &#x3C;/Prompt>
  );
}
</code></pre>
<p>That's a fairly simple prompt that just tells the LLM what data it's going to get, how to evaluate it, and then gives it the data. The LLM will then return a score that will give us some indication of how well the <code>output</code> and <code>expected</code> arrays match.</p>
<p>In the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/evals/scorers/extract-achievement-scorer.tsx">actual extract-achievement-scorer code</a> you can see how we need to import those components up at the top of the file to be able to use them in our prompt. We don't need to do that with the .mdx file approach.</p>
<p>In order to do that within the Braintrust way of doing things, we can define a scorer:</p>
<pre><code class="language-extract-achievement-scorer.tsx">export async function ExtractAchievementScorer(args: any): Promise&#x3C;Score> {
  const prompt = await renderMDX(
    &#x3C;EvaluateExtractedAchievementsPrompt
      expectedAchievements={args.expected}
      extractedAchievements={args.output}
    />
  );

  return LLMClassifierFromSpec('ExtractAchievementScorer', {
    prompt,
    choice_scores: {
      A: 1.0, // Perfect match
      B: 0.8, // Good but missing details
      C: 0.6, // Minor issues
      D: 0.3, // Major issues
      E: 0.0, // Completely wrong
    },
  })(args);
}
</code></pre>
<p>And now we can tie it all together to run all of our Experiments (all one of them, in this case) and use our <code>ExtractAchievementScorer</code> to score the comparison of the <code>output</code> and <code>expected</code> arrays:</p>
<pre><code class="language-extract-achievements.eval.ts">Eval('extract-chat-achievements', {
  data: experimentData,
  task: wrappedExtractAchievements,
  scores: [ExtractAchievementScorer],
  trialCount: 3,
  metadata: {
    model: 'gpt-4',
    description: 'Evaluating achievement extraction',
    owner: 'ed',
  },
});

// Function to wrap the async generator into a promise that resolves to an array of ExtractedAchievements
async function wrappedExtractAchievements(input: ExtractAchievementsPromptProps): Promise&#x3C;ExtractedAchievement[]> {
  return await renderExecute(input);
}
</code></pre>
<p>The <code>task</code> function is just what Braintrust runs for each Experiment to produce the output - here finally is where we use that <code>renderExecute</code> function from extract-achievements.ts to pass in our test data and get back a Promise that resolves to an array of <code>ExpectedAchievement</code> objects. The <code>experimentData</code> is the array of <code>Experiment</code> instances we defined earlier in this article.</p>
<h3>Running the Eval</h3>
<p>Running the eval now, we'll get output like this:</p>
<pre><code class="language-shell">$ npx braintrust eval  lib/ai/prompts/evals/extract-achievements.eval.ts          

Processing 1 evaluators...
Experiment mdx-prompt-polish-1737670065 is running at https://www.braintrust.dev/app/Egghead/p/extract-chat-achievements/experiments/mdx-prompt-polish-1737670065
 ████████████████████████████████████████ | extract-chat-achievements                | 100% | 3/3 datapoints


=========================SUMMARY=========================
mdx-prompt-polish-1737670065 compared to mdx-prompt-polish-1737670046:
100.00% 'ExtractAchievementScorer' score	(0 improvements, 0 regressions)

1737670065.27s 'start'            	(0 improvements, 0 regressions)
1737670072.44s 'end'              	(0 improvements, 0 regressions)
6.75s 'duration'         	(0 improvements, 0 regressions)
0.82s 'llm_duration'     	(0 improvements, 0 regressions)
2417tok 'prompt_tokens'    	(0 improvements, 0 regressions)
155.33tok 'completion_tokens'	(0 improvements, 0 regressions)
2572.33tok 'total_tokens'     	(0 improvements, 0 regressions)

See results for mdx-prompt-polish-1737670065 at https://www.braintrust.dev/app/Egghead/p/extract-chat-achievements/experiments/mdx-prompt-polish-1737670065
</code></pre>
<p>In this case we can see that our LLM as a judge generously awarded us a 100% score for our extraction of Achievements from source messages, when it compares them to the expected Achievements:</p>
<h2>Conclusions</h2>
<p>This was just implementing a single Eval to test a single LLM feature under a single scenario. For all that work we now have an automated way of quantifying how well our LLM prompt is working for this feature. The fact that we can now run these Evals at development time and in CI means that we can have confidence that we haven't unknowingly broken something as we meddle with the prompt or other code some time in the future.</p>
<h3>Pictures are useful, Types are too</h3>
<p>I made that pretty flow chart diagram before I ended up with the code in its final form. Once I had drawn that out, the render/execute/fetch cycle kinda leapt off the page and asserted itself as a fairly generalized architecture for invoking these types of prompts.</p>
<p>I found this pattern cropping up again and again, and in reality there are several ways that we want to compose those individual functions. Sometimes we want an async generator so we can stream things to the user, other times a Promise is better, other times we already have the data and just want to pass it in and get the result back, and so on.</p>
<p>Having a clear model for how your prompts work is critical to structuring evals that make sense.</p>
<h3>Good test data is useful</h3>
<p>One bonus benefit of having good Evals is that if you have good Evals you by definition have good test data, at least when it comes to rendering prompts covered by your evals. I made a page at https://www.bragdoc.ai/prompt that uses Next JS to render prompts in the browser. You can open that page (no account needed to access this page) and see exactly what the rendered prompts used by <a href="https://bragdoc.ai">bragdoc.ai</a> look like:</p>
<p>That's just a fairly simple Next JS <a href="https://github.com/edspencer/bragdoc-ai/blob/main/app/(app)/prompt/page.tsx">page.tsx</a> that uses a basic component called <a href="https://github.com/edspencer/bragdoc-ai/blob/main/app/(app)/prompt/PrettyPrompt.tsx">PrettyPrompt</a> to render three different prompts. It's currently hooked up to use that eval data, which is why you can go look at it without being logged in, but you can see how easy it would be to hook it up to real data to debug how your prompts actually get rendered.</p>
<h3>Evals are slow, expensive and difficult</h3>
<p>Your unit test suite should ideally run in about a second, triggered automatically each time you save a file in your IDE. Evals are never going to do that - they're far too slow, and sometimes the output is itself in shades of gray that just don't exist in the world of unit tests. So Evals are their own separate thing, run much less often. But still before every merge to <code>main</code>.</p>
<p>While developing <a href="https://www.bragdoc.ai">bragdoc.ai</a>, which is a fairly typical AI SaaS app, I found that whereas I could rely on Windsurf and other AI tooling for building out most of the app itself, building Evals was not something the AI excelled at. Or at least, my attempts to use AI to build evals were not very successful. After a few weeks of <a href="/2025/1/08/how-i-built-bragdoc">flying through implementation</a> when it comes to adding features, I spent the last week just basically getting Evals under control.</p>
<p>They're a lot of work, and famously difficult to get right. Bragdoc only has a handful of them so far, but I see the value in them and will continue to add them. They also unlock some really significant benefits when it comes to assessing which LLM works best for which prompt, whether it be in terms of accuracy, cost or latency. But that's a topic for another day.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/braintrust-eval-code.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[mdx-prompt: Real World Example Deep Dive]]></title>
            <link>https://edspencer.net//2025/2/3/mdx-prompt-real-world-example-deep-dive</link>
            <guid>mdx-prompt-real-world-example-deep-dive</guid>
            <pubDate>Mon, 03 Feb 2025 07:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>I just released <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a>, which is a simple library that lets you write familiar React JSX to render high quality prompts for LLMs. <a href="/2025/2/3/mdx-prompt-composable-prompts-with-jsx">Read the introductory article</a> for more general info if you didn't already, but the gist is that we can write LLM Prompts with JSX/MDX like this:</p>
<pre><code class="language-extract-commit-achievements.mdx">&#x3C;Prompt>
  &#x3C;Purpose>
    You are a careful and attentive assistant who extracts work achievements 
    from source control commit messages. Extract all of the achievements in 
    the commit messages contained within the &#x3C;user-input> tag. Follow 
    all of the instructions provided below.
  &#x3C;/Purpose>
  &#x3C;Instructions>
    &#x3C;Instruction>Each Achievement should be complete and self-contained.&#x3C;/Instruction>
    &#x3C;Instruction>If multiple related commits form a single logical achievement, combine them.&#x3C;/Instruction>
    &#x3C;Instruction>
      Pay special attention to:
      1. Code changes and technical improvements
      2. Bug fixes and performance optimizations
      3. Feature implementations and releases
      4. Architecture changes and refactoring
      5. Documentation and testing improvements
    &#x3C;/Instruction>
  &#x3C;/Instructions>
  &#x3C;Variables>
    &#x3C;Companies companies={data.companies} />
    &#x3C;Projects projects={data.projects} />
    &#x3C;today>{new Date().toLocaleDateString()}&#x3C;/today>
    &#x3C;user-instructions>
      {data.user?.preferences?.documentInstructions}
    &#x3C;/user-instructions>
    &#x3C;UserInput>
      {data.commits?.map((c) => &#x3C;Commit key={c.hash} commit={c} />)}
    &#x3C;/UserInput>
    &#x3C;Repo repository={data.repository} />
  &#x3C;/Variables>
  &#x3C;Examples
    examples={data.expectedAchievements?.map((e) => JSON.stringify(e, null, 4))}
  />
&#x3C;/Prompt>
</code></pre>
<p>This ought to look familiar to anyone who's ever seen React code. This project was born of a combination of admiration for the way IndyDevDan and others structure their LLM prompts, and frustration with the string interpolation approaches that everyone takes to generating prompts for LLMs.</p>
<p>In the <a href="/2025/2/3/mdx-prompt-composable-prompts-with-jsx">introductory post</a> I go into some details on why string interpolation-heavy functions are not great for prompts. It's a totally natural thing to want to do - once you've started programming against LLM interfaces, you want to start formalizing the mechanism by which you generate the string that is the prompt. Before long you notice that many of your app's prompts have a lot of overlap, and you start to think about how you can reuse the parts that are the same.</p>
<p>Lots of AI-related libraries try to help you here with templating solutions, but they often feel clunky. I really, really wanted to like Langchain, but I lost a day of my life trying to get it to render a prompt that I could have done in 5 minutes with JSX. JSX seems to be a pretty good fit for this problem, and anyone who knows React (a lot of people) can pick it up straight away. <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> helps React developers compose their LLM prompts with the familiar syntax od JSX.</p>
<h2>The Setup</h2>
<p>In this article we'll take a deeper look at this actual real-world example of how I use <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> for <a href="https://www.bragdoc.ai">bragdoc.ai</a> - a SaaS app that tracks your work achievements and generates documents from them. This article is not a plug for bragdoc, but I will have to tell you a little about it for the article to make sense. All of the code is open source and can be found on <a href="https://github.com/edspencer/bragdoc-ai">GitHub</a>.</p>
<h2>Prompts are arrays of tokens</h2>
<p>Our mdx-prompt powered prompt defined above will render into an XML-style prompt like this (slightly truncated for brevity):</p>
<pre><code class="language-xml">&#x3C;purpose>
  You are a careful and attentive assistant who extracts work achievements from source control commit messages. Extract all of the achievements in the commit messages contained within the &#x26;lt;user-input&#x26;gt;tag. Follow all of the instructions provided below.
&#x3C;/purpose>

&#x3C;instructions>
  &#x3C;instruction>Consider the chat history and context to understand the full scope of each achievement.&#x3C;/instruction>
  &#x3C;instruction>Each Achievement should be complete and self-contained.&#x3C;/instruction>
  &#x3C;instruction>If the user mentions multiple achievements in a single message, extract them all.&#x3C;/instruction>
  // ... more instructions
&#x3C;/instructions>

&#x3C;input-format title="You are provided with the following inputs:">
  &#x3C;companies>All of the companies that the user works at (or has worked at)&#x3C;/companies>
  &#x3C;projects>All of the projects that the user works on (or has worked on)&#x3C;/projects>
  &#x3C;user-instructions>Any specific instructions from the user to guide the extraction process&#x3C;/user-instructions>
  &#x3C;user-input>The git commits to extract achievements from&#x3C;/user-input>
  &#x3C;repository>Information about the repository the commits are from&#x3C;/repository>
&#x3C;/input-format>

&#x3C;variables>
  &#x3C;companies>
    &#x3C;company>
      &#x3C;id>74eda7d6-3c69-4f51-a53a-58624bba48f4&#x3C;/id>
      &#x3C;name>Egghead Research&#x3C;/name>
      &#x3C;role>Chief Scientist&#x3C;/role>
      &#x3C;start-date>12/31/2022&#x3C;/start-date>
      &#x3C;end-date>Present&#x3C;/end-date>
    &#x3C;/company>
  &#x3C;/companies>
  &#x3C;projects>
    &#x3C;project>
      &#x3C;id>94150287-afa1-4caa-aa66-8ad44f31120c&#x3C;/id>
      &#x3C;name>BragDoc.ai&#x3C;/name>
      &#x3C;description>AI-powered self-advocacy tool for tech-savvy individuals.&#x3C;/description>
      &#x3C;status>active&#x3C;/status>
      &#x3C;start-date>12/14/2024&#x3C;/start-date>
      &#x3C;end-date>Present&#x3C;/end-date>
      &#x3C;remote-repo-url>
      &#x3C;/remote-repo-url>
    &#x3C;/project>
    // ... more projects
  &#x3C;/projects>
  &#x3C;today>1/17/2025&#x3C;/today>
  &#x3C;user-instructions>If I don't mention a specific project, I'm talking about Brag Doc.&#x3C;/user-instructions>
  &#x3C;user-input>
    &#x3C;commit>
      &#x3C;message>Wrote a bunch of new Evals for extracting achievements and generating documents&#x3C;/message>
      &#x3C;hash>1234&#x3C;/hash>
      &#x3C;author>John Doe - john@doe.com&#x3C;/author>
      &#x3C;date>2023-01-01&#x3C;/date>
    &#x3C;/commit>
    &#x3C;commit>
      &#x3C;message>Better styling for the blog pages&#x3C;/message>
      &#x3C;hash>5678&#x3C;/hash>
      &#x3C;author>John Doe - john@doe.com&#x3C;/author>
      &#x3C;date>2023-01-02&#x3C;/date>
    &#x3C;/commit>
  &#x3C;/user-input>
  &#x3C;repository>
    &#x3C;name>bragdoc-ai&#x3C;/name>
    &#x3C;path>/path/to/bragdoc-ai&#x3C;/path>
    &#x3C;remote-url>https://github.com/edspencer/bragdoc-ai&#x3C;/remote-url>
  &#x3C;/repository>
&#x3C;/variables>

&#x3C;examples>
  &#x3C;example>
    {
      "eventStart": "2024-06-15",
      "eventEnd": "2024-09-15",
      "eventDuration": "quarter",
      "title": "Launched AI Analysis Tool with 95% Accuracy at Quantum Nexus",
      "summary": "Developed an AI tool for real-time data analysis with 95% accuracy for Quantum Nexus, playing a pivotal role in Project Orion's success.",
      "details": "As part of Project Orion at Quantum Nexus, I was responsible for developing a cutting-edge AI tool focused on real-time data analysis. By implementing advanced algorithms and enhancing the training data sets, the tool reached a 95% accuracy rate. This result significantly supported the company's research objectives and has been positively acknowledged by stakeholders for its robust performance and reliability.",
      "companyId": "e3856e75-37cf-4640-afd9-e73a53fa967d",
      "projectId": "3923129e-719b-4f99-8487-9830cf64ad5d",
      "impact": 3
    }
&#x3C;/example>
  //... more examples
&#x3C;/examples>
</code></pre>
<p>The code we're going to be looking at today is how bragdoc.ai processes git commit histories into work achievements that can then be turned into documents. Basically, if they've installed <code>npm install -g bragdoc</code>, a user can run a command like this:</p>
<pre><code class="language-bash.sh">$ bragdoc extract
</code></pre>
<p>This will grab commit messages from whatever repo you're in and send them up to the bragdoc API, which it then processes into work achievements. The LLM behind this has to consider a bunch of things in doing this, including:</p>
<ul>
<li>If the user has Projects defined already, which Project are these commits for?</li>
<li>How impactful is each achievement on a scale of 1-3?</li>
<li>When did the achievement happen?</li>
<li>Was it an achievement that happened over a period of time, or a single event?</li>
</ul>
<p>In order for it to do this, it needs to know about the user's Companies and Projects, the Commits themselves, and the Repository they're in. It may also need to know about recent Achievements that have been tracked. We're using an LLM to take a string prompt and return a structured data response, so we need to provide it with a prompt that tells it what we want it to do.</p>
<p>Moreover, in order to get an LLM to do this highly specialized task for us, we're more likely to achieve success if we one-shot a specialized prompt than if we tried to coax an LLM in the middle of a chat conversation to do it for us. This means we're probably going to use a Router LLM to essentially invoke our specialized prompt as a tool call.</p>
<h2>Router LLMs</h2>
<p>A lot of AI apps will use a Router LLM model to decide which LLM to use for a given prompt. Bragdoc does this so that we can dispatch to different prompts based on whether we're recording achievements, generating documents, or something else. From the perspective of the Router LLM, the Achievement Extraction process is just a tool that it can call with some input.</p>
<p>A tool call is not the only way that the achievement extraction process can be invoked, so this means the tool should be a thin wrapper around something else - in this case I'm calling that something else the Orchestrator, which:</p>
<ol>
<li>gathers all the data required for the Achievement Extraction prompt that wasn't provided by the Router LLM (Fetcher)</li>
<li>renders the prompt using that data (Renderer)</li>
<li>calls the LLM with the prompt, returning the processed response (LLM)</li>
</ol>
<p>As you can see there are actually at least 5 different types that our data passes through in the Achievement Extraction process:</p>
<ul>
<li>ExtractAchievementsFetcherProps - the minimal object required to fetch the data for the prompt</li>
<li>ExtractAchievementsPromptProps - the minimal object required to render the prompt</li>
<li>string - the rendered prompt we pass to the LLM</li>
<li>LLMExtractedAchievement - structured output data response from the LLM (needs processing)</li>
<li>ExtractedAchievement - final, processed Achievement objects compatible with our data layer</li>
</ul>
<p>Conceptually, the types flow around the achievement extraction process like this:</p>
<p>The reason for ExtractAchievementsFetcherProps existing is to allow the Router LLM to pass something else the minimum data required to fetch the rest of the data for the prompt. This helps the Router LLM focus on producing the right data for its tool call, and also allows us to have multiple code pathways to generate documents without going through the Router LLM.</p>
<p>The Fetcher may not even need to exist in all instances - here it's just loading the companies and projects for the given user. This centralizes that code in one place, allowing us to reuse it. Its output is ExtractAchievementsPromptProps - the minimal set of data required to render our prompt.</p>
<p>The Orchestrator is a simple function that calls the Fetcher, passes its output to the Renderer, then calls the LLM with the rendered prompt. It also does a little data transformation from the LLMExtractedAchievement to the ExtractedAchievement format that our data layer expects. Again this may not be needed in all cases - in this case it is turning date strings into JS Date objects, as well as inserting some timestamps - stuff we don't want the LLM doing anyway.</p>
<p>The Orchestrator is just the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/extract-achievements.ts">extract-achievements.ts file</a>, which contains the following functions:</p>
<ul>
<li>fetch(props) - fetches the data required for the prompt</li>
<li>render(data) - renders the prompt</li>
<li>execute(prompt) - calls the LLM with the prompt and processes the response</li>
<li>renderExecute(data) - renders the prompt and executes it, returning the extracted achievements</li>
<li>fetchRenderExecute(props) - fetch/render/execute, returns the extracted achievements as an array</li>
<li>streamFetchRenderExecute(props) - fetch/render/execute, yielding each extracted achievement as they stream in</li>
</ul>
<p>Those first three - <code>fetch</code>, <code>render</code> and <code>execute</code> - map directly to the Fetcher/Renderer/LLM conceptual diagram boxes. We can update our diagram with the actual functions, along with the three higher-level functions we've added - <code>renderExecute</code>, <code>fetchRenderExecute</code>, and <code>streamFetchRenderExecute</code>:</p>
<p>The fetch(), render(), and execute() functions are super simple. The render() function uses <a href="https://github.com/edspencer/mdx-prompt">mdx-prompt</a> to render the MDX prompt into a string. The fetch() function just asynchronously does whatever loading is required, and the execute() function uses the excellent Vercel AI SDK to call the LLM and stream back the results:</p>
<pre><code class="language-jsx">/**
 * Fetches the data necessary to render the Extract Achievements Prompt.
 * Given a minimal set of data, prepares the rest of the data required 
 * for the achievements extraction prompt
 * 
 * @param props ExtractAchievementsFetcherProps
 * @returns ExtractAchievementsPromptProps
 */
export async function fetch(props: ExtractAchievementsFetcherProps): Promise&#x3C;ExtractAchievementsPromptProps> {
  const {user, message, chatHistory} = props;

  const [projects, companies] = await Promise.all([
    getProjectsByUserId(user.id),
    getCompaniesByUserId({ userId: user.id }),
  ]);

  return {
    message,
    chatHistory,
    companies,
    projects,
    user
  }
}

/**
 * Renders the Extract Achievements Prompt
 * 
 * @param data ExtractAchievementsPromptProps
 * @returns string
 */
export async function render(data: ExtractAchievementsPromptProps): Promise&#x3C;string> {
  return await renderMDXPromptFile({
    filePath: promptPath,
    data,
    components
  });
}

/**
 * Executes the rendered prompt and yields the extracted achievements
 * 
 * @param prompt string
 * @returns AsyncGenerator&#x3C;ExtractedAchievement, void, unknown>
 */
export async function* execute(prompt: string): AsyncGenerator&#x3C;ExtractedAchievement, void, unknown> {
  const { elementStream } = streamObject({
    model: extractAchievementsModel,
    prompt,
    temperature: 0,
    output: 'array',
    schema: achievementResponseSchema,
  });

  for await (const element of elementStream) {
    yield {
      ...element,
      summary: element.summary || '',
      details: element.details || '',
      eventStart: element.eventStart ? new Date(element.eventStart) : null,
      eventEnd: element.eventEnd ? new Date(element.eventEnd) : null,
      impactSource: 'llm',
      impactUpdatedAt: new Date(),
    };
  }
}
</code></pre>
<h3>Fetch</h3>
<p>Let's take a closer look at that code then. fetch() is pretty easy:</p>
<pre><code class="language-extract-achievements.ts">/**
 * Fetches the data necessary to render the Extract Achievements Prompt.
 * Given a minimal set of data, prepares the rest of the data required 
 * for the achievements extraction prompt
 * 
 * @param props ExtractAchievementsFetcherProps
 * @returns ExtractAchievementsPromptProps
 */
export async function fetch(props: ExtractAchievementsFetcherProps): Promise&#x3C;ExtractAchievementsPromptProps> {
  const {user, message, chatHistory} = props;

  const [projects, companies] = await Promise.all([
    getProjectsByUserId(user.id),
    getCompaniesByUserId({ userId: user.id }),
  ]);

  return {
    message,
    chatHistory,
    companies,
    projects,
    user
  }
}
</code></pre>
<p>All it's doing it taking what the LLM tool call + session data from our Next Auth integration with our API endpoint gave it (ExtractAchievementsFetcherProps) and returning the data required to render the prompt (ExtractAchievementsPromptProps).</p>
<pre><code class="language-types.tsx">// props required to render the Extract Achievements Prompt
export interface ExtractAchievementsPromptProps {
  companies: Company[];
  projects: Project[];
  message: string;
  chatHistory: Message[];
  user: User;
};
</code></pre>
<p>Having a fetch step means we can call on this piece of LLM functionality from anywhere in our app without needing to re-implement the loading of projects and companies each time we do so.</p>
<h3>Render</h3>
<p>That <code>ExtractAchievementsPromptProps</code> is the same type we then pass into the Renderer, which is just a function call to mdx-prompt's renderMDXPromptFile() function:</p>
<pre><code class="language-extract-achievements.ts">//load our custom mdx-prompt components like Company and Project
import * as components from './prompts/elements';

// the path to the extract-achievements.mdx file
const promptPath = path.resolve("./lib/ai/prompts/extract-achievements.mdx");

//the render function
export async function render(data: ExtractAchievementsPromptProps): Promise&#x3C;string> {
  return await renderMDXPromptFile({
    filePath: promptPath,
    data,
    components
  });
}
</code></pre>
<p>The <code>extract-achievements.mdx</code> file looks like this (truncated for brevity - <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/extract-achievements.mdx?plain=1">here's the full thing</a>):</p>
<pre><code class="language-extract-achievements.mdx">&#x3C;Prompt>
  &#x3C;Purpose>
    You are a careful and attentive assistant who extracts work achievements
    from conversations between users and AI assistants. Extract all of the
    achievements in the user message contained within the {`&#x3C;user-input>`}
    tag. Follow all of the instructions provided below.
  &#x3C;/Purpose>
  &#x3C;Instructions>
    &#x3C;Instruction>
    Each achievement should have a clear, action-oriented title (REQUIRED) that:
    - Starts with an action verb (e.g., Led, Launched, Developed)
    - Includes specific metrics when possible (e.g., "40% reduction", "2x improvement")
    - Mentions specific systems or teams affected
    - Is between 10 and 256 characters
    &#x3C;/Instruction>
    &#x3C;Instruction>Each Achievement should be complete and self-contained.&#x3C;/Instruction>
    &#x3C;Instruction>Do not invent details that the user did not explicitly say.&#x3C;/Instruction>
  &#x3C;/Instructions>
  &#x3C;InputFormat>{data.message}&#x3C;/InputFormat>
  &#x3C;Variables>
    &#x3C;today>{new Date().toLocaleDateString()}&#x3C;/today>
    &#x3C;user-instructions>
      {data.user?.preferences?.documentInstructions}
    &#x3C;/user-instructions>
    &#x3C;ChatHistory messages={data.chatHistory} />
    &#x3C;Companies companies={data.companies} />
    &#x3C;Projects projects={data.projects} />
    &#x3C;UserInput>{data.message}&#x3C;/UserInput>
  &#x3C;/Variables>
  &#x3C;Examples examples={data.examples?.map((e) => JSON.stringify(e, null, 4))} />
&#x3C;/Prompt>

Your answer:
</code></pre>
<p>Calling the <code>render()</code> function will return a nicely formatted mixture of text and xml-style tags matching the JSX structure of the prompt.</p>
<h3>Execute</h3>
<p>The last box on the drawing above is the one labelled LLM - here's how that looks in our code. We're taking a string prompt and using an async generator function to stream the achievements back to the caller as they come in:</p>
<pre><code class="language-extract-achievements.ts">/**
 * Executes the rendered prompt and yields the extracted achievements
 * 
 * @param prompt string
 * @returns AsyncGenerator&#x3C;ExtractedAchievement, void, unknown>
 */
export async function* execute(prompt: string): AsyncGenerator&#x3C;ExtractedAchievement, void, unknown> {
  const { elementStream } = streamObject({
    model: extractAchievementsModel,
    prompt,
    temperature: 0,
    output: 'array',
    schema: achievementResponseSchema,
  });

  for await (const element of elementStream) {
    yield {
      ...element,
      summary: element.summary || '',
      details: element.details || '',
      eventStart: element.eventStart ? new Date(element.eventStart) : null,
      eventEnd: element.eventEnd ? new Date(element.eventEnd) : null,
      impactSource: 'llm',
      impactUpdatedAt: new Date(),
    };
  }
}
</code></pre>
<p>The <code>execute</code> function above is a generator function that yields ExtractedAchievement objects as they come in from the LLM. It's doing the data transformation from LLMExtractedAchievement to ExtractedAchievement that we mentioned earlier.</p>
<h3>Convenience Functions</h3>
<p>Let's remind ourselves of our diagram:</p>
<p>Turning to the 3 other functions we have defined in the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/extract-achievements.ts">extract-achievements.ts file</a>, <code>renderExecute</code> is a wrapper around render() and execute() - it's really useful for Evals, where we want to have more control over the input data than fetch() would allow. <code>fetchRenderExecute</code> just wraps the fetch part as well, returning a Promise that will resolve to the array of extracted Achievements.</p>
<p>The final function - <code>streamFetchRenderExecute</code> - is a generator function that yields each extracted achievement as it comes in from the LLM. This allows us to stream the achievements into the UI as they come in, which is what we do in the Bragdoc app. But all three of these functions are pretty simple orchestrations of the lower-level functions:</p>
<pre><code class="language-jsx">/**
 * Fetches the data, renders the prompt, and executes the prompt, yielding the extracted achievements
 * 
 * @param input ExtractAchievementsFetcherProps
 * @returns AsyncGenerator&#x3C;ExtractedAchievement>
 */
export async function* streamFetchRenderExecute(input: ExtractAchievementsFetcherProps): AsyncGenerator&#x3C;ExtractedAchievement> {
  const data = await fetch(input);

  for await (const achievement of execute(await render(data))) {
    yield achievement;
  }
}

/**
 * Fetches the data, renders the prompt, and executes the prompt, returning the extracted achievements
 * 
 * @param input ExtractAchievementsFetcherProps
 * @returns Promise&#x3C;ExtractedAchievement[]>
 */
export async function fetchRenderExecute(input: ExtractAchievementsFetcherProps): Promise&#x3C;ExtractedAchievement[]> {
  const data = await fetch(input);

  return await renderExecute(data);
}

/**
 * Renders the prompt and executes it, returning the extracted achievements
 * 
 * @param data ExtractAchievementsPromptProps
 * @returns Promise&#x3C;ExtractedAchievement[]>
 */
export async function renderExecute(data: ExtractAchievementsPromptProps): Promise&#x3C;ExtractedAchievement[]> {
  const achievements: ExtractedAchievement[] = [];
  
  for await (const achievement of execute(await render(data))) {
    achievements.push(achievement);
  }

  return achievements;
}
</code></pre>
<p>In the actual bragdoc.ai app, we try to give a better UX by streaming the Achievements in to the UI as the LLM returns them, so our tool call is actually calling <code>streamFetchRenderExecute</code> directly so that it can immediately render each Achievement into the UI.</p>
<p>Similarly, for the Evals that we run against this code, we also want to chop and change the pipeline a little bit. From the perspective of our Evals, we want to bypass the Fetcher stage so that we can provide our own ExtractAchievementsPromptProps and test that we got the right ExtractedAchievement objects back.</p>
<h2>Testing</h2>
<p>Breaking things up like this means we can write effective tests for each step of the process:</p>
<ul>
<li>Router LLM - an <strong>eval</strong> that checks the Router is calling the right tool with the right data</li>
<li>Fetcher - <strong>unit tests</strong> that check the right data is being fetched</li>
<li>Renderer - <strong>unit tests</strong> that check the right prompt is being rendered</li>
<li>LLM - an <strong>eval</strong> that checks the LLM is returning the right data for the prompt we feed it</li>
<li>Orchestrator - <strong>unit tests</strong> that check the right data is being passed between the steps</li>
</ul>
<p>In this way we've isolated our LLM invocations into tightly-defined functions, so that we can write fast-executing unit tests for everything else in our pipeline, write tight Evals against the LLM parts of the pipeline, passing in well-understood mock data matching the types we've defined.</p>
<p>But this article has already gone on long enough, so I've split the Evals stuff into its own article called <a href="/2025/2/3/edd-eval-driven-design-mdx-prompt">Eval-Driven Design with NextJS &#x26; mdx-prompt</a>.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/achievement-extraction-orchestration.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[mdx-prompt: Composable LLM Prompts with JSX]]></title>
            <link>https://edspencer.net//2025/2/3/mdx-prompt-composable-prompts-with-jsx</link>
            <guid>mdx-prompt-composable-prompts-with-jsx</guid>
            <pubDate>Mon, 03 Feb 2025 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>I'm a big fan of <a href="https://www.youtube.com/@indydevdan">IndyDevDan's YouTube channel</a>. He has greatly expanded my thinking when it comes to LLMs. One of the interesting things he does is write many of his prompts with an XML structure, like this:</p>
<pre><code class="language-xml">&#x3C;purpose>
  You are a world-class expert at creating mermaid charts.
  You follow the instructions perfectly to generate mermaid charts.
  The user's chart request can be found in the user-input section.
&#x3C;/purpose>

&#x3C;instructions>
  &#x3C;instruction>Generate valid a mermaid chart based on the user-prompt.&#x3C;/instruction>
  &#x3C;instruction>Use the diagram type specified in the user-prompt.&#x3C;/instruction>
  &#x3C;instruction>Use the examples to understand the structure of the output.&#x3C;/instruction>
&#x3C;/instructions>

&#x3C;user-input>
  State diagram for a traffic light. Still, Moving, Crash.
&#x3C;/user-input>

&#x3C;examples>
  &#x3C;example>
    &#x3C;user-chart-request>
      Build a pie chart that shows the distribution of Apples: 40, Bananas: 35, Oranges: 25.
    &#x3C;/user-chart-request>
    &#x3C;chart-response>
      pie title Distribution of Fruits
        "Apples" : 40
        "Bananas" : 35
        "Oranges" : 25
    &#x3C;/chart-response>
  &#x3C;/example>
  //... more examples
&#x3C;/examples>
</code></pre>
<p>I really like this structure. Prompt Engineering has been a dark art for a long time. We're suddenly programming using English, which is hilariously imprecise as a programming language, and it feels not quite like "real engineering".</p>
<p>But prompting is actually not programming in English, it's programming in tokens. It just looks like English, so it's easy to fall into the trap of giving it English. But we're not constrained to that at all actually - we can absolutely format our prompts more like XML and reap some considerable rewards:</p>
<ul>
<li>It's <strong>easier for humans</strong> to reason about prompts in this format</li>
<li>It's easier to <strong>reuse content</strong> across prompts</li>
<li>It's easier to have an <strong>LLM generate a prompt</strong> in this format (see <a href="https://www.youtube.com/watch?v=yZGb9-Z9DG0">IndyDevDan's metaprompt video</a>)</li>
</ul>
<h2>We've seen this before</h2>
<p>I've started migrating many of my prompts to this format, and noticed a few things:</p>
<ul>
<li>It organized my thinking around what data the prompt needs</li>
<li>Many prompts could or should use the same data, but repeat fetching/rendering logic each time</li>
</ul>
<p>For example, <a href="https://www.bragdoc.ai">bragdoc.ai</a> basically does 2 things with LLMs: Extracting Achievements from written text, and Generating Documents from Achievements. We can extract Achievements from either a chatbot message or a git commit history, so we have a separate prompt for each, but as you can imagine those prompts have a huge amount in common.</p>
<p>To each we also provide the user's project and company data, as well as any custom instructions from the user. But the Commit Extractor is given a set of commits and repo data, whereas the Text Extractor is fed a message and a chat history. The Document Generator also needs to be fed with a lot of the same data - companies and projects - but is also given a set of Achievements to generate a document from.</p>
<p>The Venn diagram would look something like this:</p>
<p>Because of all of the overlap, I'd extracted a bunch of functions that looked like this:</p>
<pre><code class="language-horrible-renderers.ts">const renderCompany = (company: Company) => {
  return `
    &#x3C;company>
      &#x3C;name>${company.name}&#x3C;/name>
      &#x3C;id>${company.id}&#x3C;/id>
      &#x3C;role>${company.role}&#x3C;/role>
      &#x3C;domain>${company.domain || 'N/A'}&#x3C;/domain>
      &#x3C;startDate>${company.startDate.toISOString()}&#x3C;/startDate>
      &#x3C;endDate>${company.endDate ? company.endDate.toISOString() : 'Present'}&#x3C;/endDate>
    &#x3C;/company>
  `;
}

const renderCompanies = (companies: Company[]) => {
  return `&#x3C;companies>` + companies.map(renderCompany).join('\n') + `&#x3C;/companies>`;
}
</code></pre>
<p>Then my prompts would do things like this:</p>
<pre><code class="language-horrible-prompt.ts">export function renderPrompt(input: ExtractAchievementsInput) {
  const chatStr = input.chatHistory
    .map(({ role, content }) => `${role}: ${content}`)
    .join('\n');

  const prompt = `Extract all achievements from the following user message. 
Consider the chat history and context to understand the full scope of each achievement.
Pay special attention to:
1. Recent updates or progress reports
2. Completed milestones or phases
3. Team growth or leadership responsibilities
4. Quantitative metrics or impact
5. Technical implementations or solutions

&#x3C;user-message>
${input.input}
&#x3C;/user-message>

&#x3C;chat-history>
${chatStr}
&#x3C;/chat-history>

&#x3C;context>
${renderCompanies(input.companies)}
${renderProjects(input.projects)}
&#x3C;/context>

More blah blah blah of unstructured instructions and exhortations.`;

  return prompt;
}
</code></pre>
<p>Ok, but this looks kinda familiar. We're just using string interpolation, but we're achieving quite a few React-y things:</p>
<ul>
<li>We're composing our prompt from smaller functions</li>
<li>We're rendering synchronously with well-defined props</li>
<li>We're rendering data into structured text</li>
</ul>
<p>Can't we just use React to do this?</p>
<h2>Use MDX for Prompt Composability and Reuse</h2>
<p>What if we could write the prompts more like this:</p>
<pre><code class="language-extract-achievements-prompt.mdx">&#x3C;Purpose>
  You are a careful and attentive assistant who extracts work achievements
  from conversations between users and AI assistants. Extract all of the
  achievements in the user message contained within the {`&#x3C;user-input>`}
  tag. Follow all of the instructions provided below.
&#x3C;/Purpose>
&#x3C;Instructions>
  &#x3C;Instruction>
  Pay special attention to:
1. Recent updates or progress reports
2. Completed milestones or phases
3. Team growth or leadership responsibilities
4. Quantitative metrics or impact
5. Technical implementations or solutions
  &#x3C;/Instruction>

  &#x3C;Instruction>
  Each achievement should have a clear, action-oriented title (REQUIRED) that:
  - Starts with an action verb (e.g., Led, Launched, Developed)
  - Includes specific metrics when possible (e.g., "40% reduction", "2x improvement")
  - Mentions specific systems or teams affected
  - Is between 10 and 256 characters
  &#x3C;/Instruction>
  &#x3C;Instruction>
  Example good titles:
  - "Led Migration of 200+ Services to Cloud Platform"
  - "Reduced API Response Time by 40% through Caching"
  - "Grew Frontend Team from 5 to 12 Engineers"
  &#x3C;/Instruction>
  &#x3C;Instruction>Do not invent details that the user did not explicitly say.&#x3C;/Instruction>
&#x3C;/Instructions>
&#x3C;InputFormat>{data.message}&#x3C;/InputFormat>
&#x3C;Variables>
  &#x3C;today>{new Date().toLocaleDateString()}&#x3C;/today>
  &#x3C;user-instructions>
    {data.user?.preferences?.documentInstructions}
  &#x3C;/user-instructions>
  &#x3C;ChatHistory messages={data.chatHistory} />
  &#x3C;Companies companies={data.companies} />
  &#x3C;Projects projects={data.projects} />
  &#x3C;UserInput>{data.message}&#x3C;/UserInput>
&#x3C;/Variables>
&#x3C;Examples examples={data.examples?.map((e) => JSON.stringify(e, null, 4))} />

Your answer:
</code></pre>
<p>The above is a slightly trimmed down version of this <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/extract-achievements.mdx?plain=1">extract-achievements.mdx</a> file in the <a href="https://github.com/edspencer/bragdoc-ai/tree/main/lib/ai/prompts">prompts directory</a> of the bragdoc-ai repository. First of all, what are we even looking at here? This is MDX, a mature and well-supported mashup of JSX and Markdown. I use it for this very blog site, and it has some nice attributes for writing prompts:</p>
<ul>
<li>It supports plain, unstructured text</li>
<li>It supports JSX, allowing us to create React components for our prompts</li>
<li>It supports XML, so we can use the XML-style prompt syntax for structured instructions that don't need a full React component</li>
</ul>
<p>It renders to something like this:</p>
<pre><code class="language-rendered-prompt.xml">&#x3C;purpose>
  You are a careful and attentive assistant who extracts work achievements
  from conversations between users and AI assistants. Extract all of the
  achievements in the user message contained within the &#x26;#x3C;user-input>
  tag. Follow all of the instructions provided below.
&#x3C;/purpose>
&#x3C;instructions>
  &#x3C;instruction>
    Pay special attention to:
    - Recent updates or progress reports
    - Completed milestones or phases
    - Team growth or leadership responsibilities
    - Quantitative metrics or impact
    - Technical implementations or solutions
    
  &#x3C;/instruction>
  &#x3C;instruction>
    Each achievement should have a clear, action-oriented title (REQUIRED) that:
    - Starts with an action verb (e.g., Led, Launched, Developed)
    - Includes specific metrics when possible (e.g., "40% reduction", "2x improvement")
    - Mentions specific systems or teams affected
    - Is between 10 and 256 characters
    
  &#x3C;/instruction>
  &#x3C;instruction>
    Example good titles:
    - "Led Migration of 200+ Services to Cloud Platform"
    - "Reduced API Response Time by 40% through Caching"
    - "Grew Frontend Team from 5 to 12 Engineers"
    
  &#x3C;/instruction>
  &#x3C;instruction>Do not invent details that the user did not explicitly say.&#x3C;/instruction>

  //... more instructions
&#x3C;/instructions>
&#x3C;input-format title="You are provided with the following inputs:">Hello&#x3C;/input-format>
&#x3C;variables>
  &#x3C;today>2/3/2025&#x3C;/today>
  &#x3C;user-instructions>If I don't mention a specific project, I'm talking about Brag Doc.&#x3C;/user-instructions>
  &#x3C;chat-history>&#x3C;/chat-history>
  &#x3C;companies>
    &#x3C;company>
      &#x3C;id>7972262d-c63d-4c87-b449-24dc634ca152&#x3C;/id>
      &#x3C;name>Egghead Research&#x3C;/name>
      &#x3C;role>Chief Scientist&#x3C;/role>
      &#x3C;start-date>12/31/2022&#x3C;/start-date>
      &#x3C;end-date>Present&#x3C;/end-date>
    &#x3C;/company>
    &#x3C;company>
      &#x3C;id>65e274f2-4f5a-4e68-89ca-0fcf9c4898cb&#x3C;/id>
      &#x3C;name>Palo Alto Networks&#x3C;/name>
      &#x3C;role>Principal Engineer&#x3C;/role>
      &#x3C;start-date>1/31/2016&#x3C;/start-date>
      &#x3C;end-date>9/29/2021&#x3C;/end-date>
    &#x3C;/company>
  &#x3C;/companies>
  &#x3C;projects>
    &#x3C;project>
      &#x3C;id>24ac74b8-dfe6-4fdc-bf34-4a6b9ee22be6&#x3C;/id>
      &#x3C;name>BragDoc.ai&#x3C;/name>
      &#x3C;description>AI-powered self-advocacy tool for tech-savvy individuals.&#x3C;/description>
      &#x3C;status>active&#x3C;/status>
      &#x3C;company-id>7972262d-c63d-4c87-b449-24dc634ca152&#x3C;/company-id>
      &#x3C;start-date>12/14/2024&#x3C;/start-date>
      &#x3C;end-date>Present&#x3C;/end-date>
      &#x3C;remote-repo-url>&#x3C;/remote-repo-url>
    &#x3C;/project>
    &#x3C;project>
      &#x3C;id>9af96ca7-614e-4566-bb57-f4376a393c43&#x3C;/id>
      &#x3C;name>mdx-prompt&#x3C;/name>
      &#x3C;description>Composable LLM prompts with JSX and MDX&#x3C;/description>
      &#x3C;status>active&#x3C;/status>
      &#x3C;company-id>7972262d-c63d-4c87-b449-24dc634ca152&#x3C;/company-id>
      &#x3C;start-date>12/31/2022&#x3C;/start-date>
      &#x3C;end-date>6/29/2023&#x3C;/end-date>
      &#x3C;remote-repo-url>&#x3C;/remote-repo-url>
    &#x3C;/project>
  &#x3C;/projects>
  &#x3C;user-input>Hello&#x3C;/user-input>
&#x3C;/variables>
&#x3C;examples>
  &#x3C;example>
    {
    "eventStart": "2024-06-15",
    "eventEnd": "2024-09-15",
    "eventDuration": "quarter",
    "title": "Launched AI Analysis Tool with 95% Accuracy at Quantum Nexus",
    "summary": "Developed an AI tool for real-time data analysis with 95% accuracy for Quantum Nexus, playing a pivotal role in Project Orion's success.",
    "details": "As part of Project Orion at Quantum Nexus, I was responsible for developing a cutting-edge AI tool focused on real-time data analysis. By implementing advanced algorithms and enhancing the training data sets, the tool reached a 95% accuracy rate. This result significantly supported the company's research objectives and has been positively acknowledged by stakeholders for its robust performance and reliability.",
    "companyId": "e3856e75-37cf-4640-afd9-e73a53fa967d",
    "projectId": "3923129e-719b-4f99-8487-9830cf64ad5d",
    "impact": 2
    }
  &#x3C;/example>
  //... more examples
&#x3C;/examples>
Your answer:
</code></pre>
<p>The <code>&#x3C;Purpose></code>, <code>&#x3C;Instructions></code>, and <code>&#x3C;Variables></code> tags are all just very basic JSX components exported by the <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> library. They're part of the standard package of core components that come with mdx-prompt, but they're super basic and it's easy to make your own.</p>
<p>You can imagine how similar the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/extract-commit-achievements.mdx?plain=1">extract-commit-achievements.mdx</a> prompt looks, with a lot of reused components and a tweaked prompt and set of instructions. The <a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/generate-document.mdx?plain=1">generate-document.mdx</a> prompt looks quite similar, with the same companies and projects components being rendered, but also a set of achievements to generate a document from.</p>
<p>Beyond the built-in components, we also used a bunch of our own up there - chiefly the <code>Company</code> and <code>Projects</code> components. They're just normal React components (<a href="https://github.com/edspencer/bragdoc-ai/blob/main/lib/ai/prompts/elements.tsx">elements.tsx</a>):</p>
<pre><code class="language-jsx">export function Company({ company }: { company: CompanyType }) {
  return (
    &#x3C;company>
      &#x3C;id>{company.id}&#x3C;/id>
      &#x3C;name>{company.name}&#x3C;/name>
      &#x3C;role>{company.role}&#x3C;/role>
      &#x3C;start-date>{company.startDate.toLocaleDateString()}&#x3C;/start-date>
      &#x3C;end-date>{company.endDate?.toLocaleDateString() || 'Present'}&#x3C;/end-date>
    &#x3C;/company>
  );
}

export function Companies({ companies }: { companies: CompanyType[] }) {
  return (
    &#x3C;companies>
      {companies.map((company) => (
        &#x3C;Company key={company.id} company={company} />
      ))}
    &#x3C;/companies>
  );
}
</code></pre>
<p>That's what <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> lets you do. It's a simple library that lets you write your prompts in JSX, and then render them to a string. It works great alongside React.</p>
<h2>Benefits</h2>
<p>We get a number of benefits from this:</p>
<ul>
<li>Reuse of components such as <code>&#x3C;Companies /></code> and <code>&#x3C;Projects /></code></li>
<li>Composability of prompts, where we can easily add or remove sections</li>
<li>JSX syntax highlighting and linting</li>
<li>Familiarity with JSX for React developers</li>
<li>A well-defined set of props required to render the prompt</li>
</ul>
<p>That last one is important. By creating a JSX prompt in this way, we've forced ourselves to distill down to the essential data that the prompt needs, as expressed in our <code>ExtractAchievementsPromptProps</code> type. Not only does this make it easier to understand what data we need to assemble for the prompt, it also makes it easier run evals against the prompt with mock data:</p>
<pre><code class="language-ExtractAchievementsPromptProps.ts">// It's much easier to reason about what a prompt needs with an interface.
// Much easier to feed test and eval data to as well.
export interface ExtractAchievementsPromptProps {
  companies: Company[];
  projects: Project[];
  message: string;
  chatHistory: Message[];
  user: User;
}
</code></pre>
<p>I didn't want this article to be too long, so I'm publishing 2 other articles at the same time that go deeper into this. The first is <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">mdx-prompt: Real World Example Deep Dive</a>, where we look at a real-world example of mdx-prompt being using in a production open source Next JS application. The second is <a href="/2025/2/3/edd-eval-driven-design-mdx-prompt">EDD: Eval-Driven-Design with mdx-prompt</a>, where we look at how to write tests for these prompts.</p>
<h2>Downsides / Challenges</h2>
<p>I had to swim uphill a little to get this working in all the different places we want it to, chiefly when it comes to rendering:</p>
<h3>React / Next JS compatibility quirks</h3>
<p><code>mdx-prompt</code> needs to work in a bunch of different places:</p>
<ul>
<li>Rendering in API endpoints to power LLM calls</li>
<li>Rendering in CLI functions like <code>npx braintrust eval</code></li>
<li>Working in test environments like Jest</li>
<li>Rendering preview prompts into your UI a la https://www.bragdoc.ai/prompt</li>
</ul>
<p>I spend most of my time in Next JS and don't have a full mental model of its integration with React. A bunch of times while creating <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> I ran into problems with incompatible React versions. Some of that was just a bad rollup configuration, but <a href="https://github.com/vercel/next.js/discussions/57631">annoying problems abound</a> and this stuff can be a little quirky to get running in all places at once.</p>
<p>I never did find a way to have a React Server Component render one of the prompts to text, which is what I wanted to be able to do in an RSC along with the <a href="https://bright.codehike.org/">Bright syntax highlighting library</a>. In the end I used a server endpoint to render the prompt to text and use a client component to fetch that text via an API call, but it's slightly inelegant. On the other hand, it is interesting to have API endpoints that return well-structured LLM prompts. Maybe that will be useful elsewhere.</p>
<h3>Some TypeScript chores</h3>
<p>Obviously, most of these XML tags that I'm using in my prompts don't exist in the HTML spec, so TypeScript is not happy with you. In Next JS, I've found that you can just declare them as <code>JSX.IntrinsicElements</code> and TypeScript will be happy. In my Next JS app I just created a <a href="https://github.com/edspencer/bragdoc-ai/blob/main/global.d.ts">global.d.ts</a> file like this:</p>
<pre><code class="language-global.d.ts">import React from 'react';

type CustomHTMLProps = React.DetailedHTMLProps&#x3C;
  React.HTMLAttributes&#x3C;HTMLElement>,
  HTMLElement
>;

// Define any custom tags you want to permit for your LLM prompts here
// [I've severely truncated this list]
type CustomTags =
  | 'companies'
  | 'company'
  | 'projects'
  | 'project'
  | 'name'
  | 'remote-url';

//adds all the custom tags to the JSX namespace
declare module 'react' {
  namespace JSX {
    interface IntrinsicElements extends Record&#x3C;CustomTags, CustomHTMLProps> {}
  }
}
</code></pre>
<p>Ok, that's cool and the TypeScript errors go away and everything builds, tests and runs just fine. It's a bit annoying though. The actual CustomTags is a lot longer than that. Maybe there's some better TypeScript or JSX trick that could make this more pleasant.</p>
<p>Note though that you only need to do this for your custom React components - any xml-style tags you use in your .mdx files will just get rendered as text as expected.</p>
<h3>Abuse of ReactDOM</h3>
<p>Ultimately this is a bit of a hack, and because it's using ReactDOM to render the JSX to a string, it also has edge-case bugs like the one where you use a <code>&#x3C;title>Some Title&#x3C;/title></code> tag as part of your JSX prompt only for the <code>&#x3C;title></code> to get hoisted to the top of the document because ReactDOM thinks it's rendering an HTML document. There's probably other stuff like that.</p>
<p>Of course, React renders text just fine too, so you can totally just use <a href="https://github.com/edspencer/mdx-prompt">mdx-prompt</a> without using xml-style tags and still benefit from all the composability and reuse stuff.</p>
<h3>We're not importing in the usual way</h3>
<p>As I write about in the deep-dive post, we're using JSX/MDX to author and render our prompts. The way we do that at the moment is like this:</p>
<pre><code class="language-extract-achievements.ts">import { renderMDXPromptFile } from "mdx-prompt";
import * as components from './prompts/elements';

const promptPath = path.resolve("./lib/ai/prompts/extract-achievements.mdx");

/**
 * Renders the Extract Achievements Prompt
 * 
 * @param data ExtractAchievementsPromptProps
 * @returns string
 */
export async function render(data: ExtractAchievementsPromptProps): Promise&#x3C;string> {
  return await renderMDXPromptFile({
    filePath: promptPath,
    data,
    components
  });
}
</code></pre>
<p>The <code>render()</code> function returns the rendered prompt as a string. We're using the <code>renderMDXPromptFile</code> helper from <a href="https://github.com/edspencer/mdx-prompt/">mdx-prompt</a> to do the rendering, which is in turn using ReactDOM to render the JSX to a string. This is part of what allows us to not have to import all (or indeed any) of the components that our <code>.mdx</code> files use - the <code>.mdx</code> prompt files can just
render the <code>&#x3C;Purpose /></code>, <code>&#x3C;Instructions /></code>, and other built-in mdx-prompt components, and we pass in all of our app's custom ones defined in elements.tsx.</p>
<p>There are ups and downs associated with this - one issue is that we lose type checking, which I discuss a little more in the <a href="./deep-dive.md">deep-dive</a> post. It's also maybe possible that some bundlers could have difficulty seeing the import and leave out the file from the build, though it's worked just fine for me in my Next JS apps.</p>
<h2>Integrating into the UI</h2>
<p>One of the appealing things about writing prompts with JSX/MDX is that you can just render it like any other React component. This makes it fairly easy to render our prompts into the UI, so that we can iterate on them, feed them different data, etc. It really does beat console.logging prompts to the terminal, where we can't benefit from syntax highlighting and it's easy to just lose things in the noise.</p>
<p>The fact that mdx-prompt is just React is a little deceptive, though, as when we render React components into the browser, we ultimately want the DOM API to turn the tags into DOM elements, whereas we just want our mdx-prompt to be rendered as a string.</p>
<p>I made a page at https://www.bragdoc.ai/prompt that uses Next JS to render prompts in the browser. You can open that page (no account needed to access this page) and see exactly what the rendered prompts used by <a href="https://bragdoc.ai">bragdoc.ai</a> look like:</p>
<p>I tried to do that in an <a href="/blog/tag/rsc">RSC</a> but Next JS really doesn't want you rendering React components into strings in server components. It seems to be a somewhat common issue judging by the <a href="https://github.com/vercel/next.js/discussions/57631">GitHub issues</a> of <a href="https://github.com/vercel/next.js/discussions/69244">people discussing</a> how to <a href="https://github.com/vercel/next.js/issues/43810">get around it</a>. In the end I just decided to create a simple API to render the prompt to a string, then fetch it via SWR in the browser:</p>
<pre><code class="language-route.ts">import { NextResponse } from 'next/server';
import { render as renderExtractAchievements } from '@/lib/ai/extract-achievements';
import { render as renderExtractCommitAchievements } from '@/lib/ai/extract-commit-achievements';
import { render as renderGenerateDocument } from '@/lib/ai/generate-document';

type Params = Promise&#x3C;{
  id: string;
}>;

import {
  companies,
  projects,
  user,
  repository,
  commits,
} from '@/lib/ai/prompts/evals/data/user';

import { chatHistory, expectedAchievements as examples } from '@/lib/ai/prompts/evals/data/extract-achievements';
import { existingAchievements } from '@/lib/ai/prompts/evals/data/weekly-document-achievements';

// This is a Server Route, so no "use client" here
export async function GET(
  request: Request,
  { params }: { params: Params }
) {
  const { id } = await params;

  let input: any;
  let prompt =  '';

  switch (id) {
    case 'extract-achievements':
      input = {
        user,
        message: 'Hello',
        chatHistory,
        projects,
        companies,
        examples
      }

      prompt = await renderExtractAchievements(input);
      break;
    case 'extract-commit-achievements':
      input = {
        user,
        companies,
        projects,
        repository,
        commits,
      }

      prompt = await renderExtractCommitAchievements(input);

      break;
    case 'generate-document':
      prompt = await renderGenerateDocument({
        user,
        docTitle: 'Weekly Update',
        days: 7,
        achievements: existingAchievements,
        project: projects[0],
        company: companies[0],
        userInstructions: 'Always use the title "Weekly Update"'
      });

      break;
  }

  // 4) Return the HTML as a text or HTML response
  return new NextResponse(prompt, {
    status: 200,
    headers: {
      'Content-Type': 'text/html',
    },
  });
}
</code></pre>
<p>It's not quite as idiomatic as passing props to React components, and I don't love switching over the id in this way as it won't scale particularly well, but it does give us a really easy way to render a nicely formatted prompt to a string, using fake data in this case, but it's also easy to <a href="https://github.com/edspencer/bragdoc-ai/blob/main/app/api/prompts/%5Bid%5D/user/route.ts">integrate it with the session</a>, your database, or whatever you need.</p>
<p>This is another way that Evals prove their worth - having good data for Evals means by definition that you have good data to render your prompts with. It's trivial to throw together an <a href="https://github.com/edspencer/bragdoc-ai/blob/main/app/(app)/prompt/page.tsx">app/prompts/page.tsx</a> file with contents like the above and a few seconds later see the full prompt rendered in your browser, instead of searching for it in terminal output or logs.</p>
<p>In <a href="/2025/2/3/mdx-prompt-real-world-example-deep-dive">part 2</a>, we'll go through a <a href="(/2025/2/3/mdx-prompt-real-world-example-deep-dive)">complete example of how bragdoc.ai uses mdx-prompt</a> for all of its core LLM capabilities. Then, in <a href="/2025/2/3/edd-eval-driven-design-mdx-prompt">part 3</a> we take a more in-depth look at how easy it is to <a href="/2025/2/3/edd-eval-driven-design-mdx-prompt">create Evals with mdx-prompt</a>. In 2025 Evals really are table stakes for any AI app, and they're worth embracing early as they'll almost certainly improve what may well be the most important part of your app.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/prompts-venn-diagram.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How I built bragdoc.ai in 3 weeks]]></title>
            <link>https://edspencer.net//2025/1/8/how-i-built-bragdoc</link>
            <guid>how-i-built-bragdoc</guid>
            <pubDate>Wed, 08 Jan 2025 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Feel free to run your own version if you don't want to pay me the $2.50/month for the hosted version. But mostly it's there as a reference for how to build a product like this with AI tooling.</p>
<p>As we start 2025, it's never been faster to get a SaaS product off the ground. The frameworks, vendors and tools available make it possible to build in weeks what would have taken months or years even just a couple of years ago.</p>
<p>But it's still a lot.</p>
<p>Even when we start from a base template, we still need to figure out our data model, auth, deployment strategy, testing, email sending/receiving, internationalization, mobile support, GDPR, analytics, LLM evals, validation, UX, and a bunch more things:</p>
<p>This morning I launched <a href="https://www.bragdoc.ai">bragdoc.ai</a>, an AI tool that tracks the work you do and writes things like weekly updates &#x26; performance review documents for you. In previous jobs I would keep an <code>achievements.txt</code> file that theoretically kept track of what I worked on each week so that I could make a good case for myself come review time. Bragdoc scratches my own itch by keeping track of that properly with a chatbot who can also make nice reports for me to share with my manager.</p>
<p>But this article isn't much about <a href="https://bragdoc.ai">bragdoc.ai</a> itself, it's about how a product like it can be built in 3 weeks by a single engineer. The answer is AI tooling, and in particular the Windsurf IDE from Codeium.</p>
<p>In fact, this article could easily have been titled "Use Windsurf or Die". I've been in the fullstack software engineering racket for 20 years, and I've never seen a step-change in productivity like the one heralded by Cursor, Windsurf, Repo Prompt and the like. We're in the first innings of a wave of change in how software is built.</p>
<h2>Productivity Pays</h2>
<p>Any time I can add 5% to my productivity, I grab it with both hands. Last year I wrote about what I think is <a href="/2024/6/25/hardware-setup-for-software-engineers-2024">the best hardware setup for software engineers</a>. I guesstimate that I get about a 20% productivity bump from that vs a standard 2 monitor setup, or 70% vs just a laptop screen. I spent years and thousands of dollars chasing that 20%. It's a big deal.</p>
<p>And then Windsurf came along, and within a few days my output across a full afternoon of work was consistently double what it would usually have been. I strongly suspect it will double again in the next year or so, then double again after that.</p>
<p>Because the amount we get paid is directly proportional to the value we create for our employers, maximizing our output is the most important thing we can do for our careers. This is as it should be. Embrace these tools or get out-competed by those who do.</p>
<p>This type of tooling is already the greatest productivity booster I've seen in my career, and it hasn't come close to its full potential yet. AI tooling for software engineering is no longer optional.</p>
<h2>The Stack</h2>
<p>So let's dive in to how I built <a href="https://www.bragdoc.ai">bragdoc.ai</a> in 3 weeks. The first ingredient was 20 years of experience doing this sort of thing. That's the hardest part of this equation and the reason why I think senior engineers are set to benefit most from these tools (see Winners and Losers below). But whether you have that or not, the following ingredients are a pretty good place to start:</p>
<ul>
<li><a href="https://vercel.com/templates/next.js/nextjs-ai-chatbot">Vercel Chat</a> - Excellent starting template for a chatbot++ AI project</li>
<li><a href="https://codeium.com">Windsurf</a> - The AI IDE that makes it all possible (see also: <a href="https://www.cursor.com">Cursor</a>, <a href="https://repoprompt.com/">Repo Prompt</a>)</li>
<li><a href="https://sdk.vercel.ai/">Vercel AI SDK</a> - Excellent library for all kinds of LLM calls. Already baked in to the Vercel Chat template.</li>
<li><a href="https://tailwindui.com/">Tailwind UI</a> - Great collection of well-designed components that make it easy to build a good looking UI quickly</li>
<li><a href="https://www.braintrust.dev/">Braintrust</a> - Easy tracking for LLM Evals, which are unit tests for your AI calls</li>
<li><a href="https://www.flowvoice.ai/">Wispr Flow</a> - Because we're just prompting, it's often much faster/easier to speak than to type</li>
<li><a href="https://vercel.com">Vercel</a> - Hosting, CI/CD, and a bunch of other stuff that makes it easy to <a href="/2024/6/27/teams-using-nextjs-vercel-advantage">get a product off the ground quickly</a></li>
</ul>
<p>There are other things in the mix, obviously, but those are the main ingredients. Now let's look at how I use them.</p>
<h2>Building things quickly in 2025</h2>
<p>So here are my top tips on how to use the current set of AI software engineering tooling effectively to increase your output.</p>
<p>I'm couching these in terms of using WindSurf, but the same principles apply to Cursor, Repo Prompt, and any other AI tooling that comes along.</p>
<h3>.windsurfrules</h3>
<p>Absolutely critical in all this is setting up a system prompt for the LLMs doing work for you. <a href="https://www.youtube.com/watch?v=aG-utUqVrb0">Yifan's YouTube video on this</a> explains it so clearly that I won't try to do better here. The same things he talks about for Cursor apply to equally to Windsurf. My <a href="https://github.com/edspencer/bragdoc-ai/blob/main/.windsurfrules">.windsurfrules</a> file for bragdoc is a couple of hundred lines long, with a dozen edits as I iterate on it.</p>
<p>Doing the initial work to learn about that and implement that file took several hours. The second time I do that it'll take 30 minutes. But that time invested is critical in getting the AI tooling to do what you actually want. It means you don't have to keep repeating yourself about how you want things done when you talk to the bot, but can focus on the actual work you want done.</p>
<h3>README.md and FEATURES.md</h3>
<p>LLMs still have pretty limited context windows, and any application of even moderate complexity will quickly exceed that. When I work on a codebase that I'm familiar with, I have a mental model of most of the stuff in that codebase, but LLMs don't have that benefit so we need to give them a hand.</p>
<p>Being able to point Windsurf at high quality documentation (README.md) and a description of all of the features that already exist (FEATURES.md) can give it that mental model without chewing up a ton of context. The other benefit of having a summarization of your entire codebase is that current generation LLMs start forgetting things when the context is long, so shorter chat sessions tend to produce better results. You can of course use Windsurf itself to keep those files updated too.</p>
<h3>Feature Implementation Process</h3>
<p>When I start working on a new feature - let's say it's support for assigning an impact rating to an Achievement - I start a new chat session inside Windsurf and tell it something like this:</p>
<p>The <a href="https://github.com/edspencer/bragdoc-ai/blob/main/features/impact/requirements.md">generated requirements.md</a> will usually miss the mark on a few things so I will typically need to ask Windsurf to fix a few things, but we quickly converge on a detailed document that describes the feature at a level that a junior engineer should have no trouble implementing. In this instance you can see I actually had it create a <a href="https://github.com/edspencer/bragdoc-ai/blob/main/features/impact/plan.md">PLAN.md</a> file as well, which is a more detailed breakdown of the work to be done. Sometimes that can yield better results, especially for complex tasks.</p>
<p>Once we've got that, I'll commit it to a new branch, and ask Windsurf to start implementation. I ask it to keep this file updated as it proceeds with the work, and to keep a <a href="https://github.com/edspencer/bragdoc-ai/blob/main/features/impact/log.md">log.md</a> file in the same directory that describes the work it's done. This way I can easily start new chat sessions to reset to a short LLM context and therefore better results.</p>
<p>From there it's generally a matter of just iteratively asking Windsurf to proceed with the next part of the implementation. It will take some number of steps before it stops and asks you what to do next - for the <a href="https://github.com/edspencer/bragdoc-ai/blob/main/features/impact/requirements.md">impact feature requirements.md</a> I probably had to prompt it a couple of dozen times to get to a complete implementation. At least 50% of the edits it makes result in TypeScript errors, so there is some tedious work fixing those, but it's still enormously faster than typing it all out myself.</p>
<p>The final <a href="https://github.com/edspencer/bragdoc-ai/pull/9">PR for the impact feature</a> shows that I made <a href="https://github.com/edspencer/bragdoc-ai/pull/9/commits">14 commits</a> over about a 90 minute session to get it done, with about <a href="https://github.com/edspencer/bragdoc-ai/pull/9/files">1000 LOC changed</a>. Committing often is highly recommended, as it's easy for Windsurf to do completely the wrong thing and you want to be able to roll back to a known good state.</p>
<p>Several hundred lines of that are the documents that Windsurf creates and uses to keep itself on track. Once the implementation is finished, one could and probably should delete the contents of the features/impact directory, as it's unlikely to be useful in the future, but one nice side benefit here is that we can implement some of the feature, go work on something else, then come back and finish it later as if we'd never left.</p>
<h3>Tracking TODOs and bugs</h3>
<p>I'd love to be able to use GitHub issues, and I'm sure that's the way it will go in the coming months, but for now I use a simple <a href="https://github.com/edspencer/bragdoc-ai/blob/main/TODO.md">TODO.md</a> to track issues and work that needs to be done. Until these tools get a native ability to CRUD issues in whatever issue tracker you want, this approach seems to work well enough for a single developer. Obviously, it won't scale well.</p>
<p>As with most of the other docs in the repo, a lot of the TODO.md was written by Windsurf itself in response to a prompt. Six months from now this approach will probably make no sense any more.</p>
<h3>Disclaimers &#x26; Caveats</h3>
<p>I have no affiliation with Codeium other than being a happy customer.</p>
<p>All the work I've been doing with Windsurf is stuff that the various parts of it have probably been trained on a lot. TypeScript projects using NextJS and React, a fairly common set of libraries, established API patterns, etc.</p>
<p>Bragdoc currently has about 500k tokens of content, between the code and the documentation. Both of those are really important to Windsurf. Many React applications will have 10x that. At the moment, none of the models available in Windsurf can deal with that much context simultaneously, so it is prone to forgetting things. I'm sure there are already various tricks in place to reduce that, but also it seems likely that models will continue to support larger and larger contexts lengths. It will only get better at this, and it's plausible that it will get close enough to perfect to stop being frustrating.</p>
<p>I highly recommend <a href="https://www.youtube.com/@indydevdan">IndyDevDan's YouTube Channel</a> for insights about how to use these tools but also skate to where the puck is heading. Many of these caveats are going to disappear in the next year or so.</p>
<h2>It's like a junior engineer</h2>
<p>Windsurf is a junior engineer from the seventh dimension, in that it's highly capable at many things, needs guidance in others, and sometimes does things that make no sense whatsoever.</p>
<p>50% of the TypeScript file changes it makes result in type errors, which you then have to go through and fix. Often if you tell it "TS errors in index.ts" it will fix them, but it is frustrating to have to do that. I'm sure that will get fixed before long, and will make a material difference in the output once again.</p>
<h2>There's actually a lot more to be had</h2>
<p>So after a few weeks of using it, I'm pretty satisfied that Windsurf is generally able to compress a day's work into an afternoon. However, it actually feels both fast and slow at the same time at the moment. I haven't taken actual measurements, so pinch of salt and all that, but it will often take around 10 seconds to make a change to a file, so when it chains 10+ edits together (which is amazing), it can take a minute or so to complete.</p>
<p>This part needs to get 10x faster, and I'm sure it will. At that point I think it will be able to compress work that would have taken a day in 2023 into about an hour.</p>
<h2>Winners and Losers</h2>
<p>This is a game-changing technology that is likely to disrupt our entire industry. As is often the case, it will probably benefit some a lot more than others. Putting my old Engineering Manager hat on, if I want to increase the velocity of my team, I don't want to add more junior engineers, I want a faster Windsurf for my senior folks. And I'm sure that's what I'll get before long. I wouldn't want to be a junior engineer in the job market in 2025.</p>
<p>Leetcode's days are numbered for the same reason we don't ask people to do long division by hand in interviews. Companies will no longer be able to perform the mental gymnastics required to ask a candidate to solve a logic problem they will never encounter, without the aid of a tool they will definitely have available and would be foolish not to use. Too many heads will implode, but more importantly too much money is at stake.</p>
<p>Focus will switch towards experience and ability to use those very tools to have high output for the company. Capitalism demands it. There was already a high dynamic range in software engineering - engineers who could create 10x more value than others. These AI tools add another order of magnitude to that range.</p>
<p>That ought to be across the board but it's possible that the old timers won't embrace it as fast as the younger engineers, so there may be an opportunity for some to close that gap a little.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/build-fast-ai.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[NarratorAI: Trainable AI assistant for Node and React]]></title>
            <link>https://edspencer.net//2024/10/4/introducing-narrator-ai</link>
            <guid>introducing-narrator-ai</guid>
            <pubDate>Fri, 04 Oct 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Every word in every article on this site was, for better or worse, written by me: a real human being. Recently, though, I realized that various pages on the site kinda sucked. Chiefly I'm talking about the <a href="/blog">Blog home page</a>, tag pages <a href="/blog/tag/ai">like this one for articles tagged with AI</a> and other places where I could do with some "meta-content".</p>
<p>By meta-content I mean content about content, like the couple of short paragraphs that summarize recent posts for a tag, or the outro text that now appears at the end of each post, along with the <a href="/2024/9/12/read-next-ai-content-recommendations-node">automatically generated Read Next recommendations</a> that I added recently using <a href="/2024/9/12/read-next-ai-content-recommendations-node">ReadNext</a>.</p>
<p>If you go look at the <a href="/blog/tag/rsc">RSC tag</a>, for example, you'll see a couple of paragraphs that summarize what I've written about regarding <a href="/blog/tag/rsc">React Server Components</a> recently. The list of article excerpts underneath it is a lot more approachable with that high-level summary at the top. Without the intro, the page just feels neglected and incomplete.</p>
<p>But the chances of me remembering to update that intro text every time I write a new post about React Server Components are slim to none. I'll write it once, it'll get out of date, and then it will be about as useful as a chocolate teapot. We need a better way. Ideally one that also lets me play by watching the AI stream automatically generated content before my very eyes:</p>
<h2>AI to the rescue</h2>
<p>Although I don't want AI generating my actual content, I'm happy to let it generate the meta-content that surrounds it. That's what NarratorAI does. <a href="https://github.com/edspencer/narrator-ai">NarratorAI</a> is a pair of NPM packages that create and present AI-generated content that supports your actual content. It's a bit like a content assistant that writes the boring bits for you:</p>
<ul>
<li><strong><a href="https://www.npmjs.com/package/narrator-ai">narrator-ai</a></strong>: The core package that generates the content</li>
<li><strong><a href="https://www.npmjs.com/package/@narrator-ai/react">@narrator-ai/react</a></strong>: A React library that helps render, regenerate and rate the content</li>
</ul>
<p>You can use <code>narrator-ai</code> whether you use React or not, but it does go well together. You don't need to use <code>@narrator-ai/react</code> if you don't want to, but it does some nice things for you like letting you easily regenerate and rate the content that <code>narrator-ai</code> generates (see gif above and live demo below).</p>
<h2>Live Demo me already</h2>
<p>This little thing inside the box below is a demo of NarratorAI in action. It's showing you the "What to Rad Next" text for this very post (scroll down to the bottom of this article to see that). This piece of content was generated by <code>narrator-ai</code>, and I'm using <code>@narrator-ai/react</code> below to render it.</p>
<p>Although I use <code>@narrator-ai/react</code> to render this type of content throughout the site, I only enable the editorial action buttons when I'm developing locally. For this demo below, though, I've enabled the regenerate, thumbs up and thumbs down buttons for you to play with live, as well as chosen a lovely shade of red to make things stand out:</p>
<p></p>
<p>The regenerate button will stream in a new piece of Markdown content generated by NarratorAI. The thumbs up and thumbs down buttons will rate the content, which will help NarratorAI learn what you like and don't like. You can provide a reason why you did/didn't like the content, and that feedback will be used to improve subsequent generations.</p>
<p>Don't worry, you can't break anything - although it's all hooked up to a real backend, the demo above won't overwrite anything.</p>
<h2>How it works</h2>
<p>NarratorAI uses a technique called Few Shot Prompting to generate content. This is where we give an LLM a few examples of the type of content we want it to generate, and then ask it to generate more of the same. Equally importantly, we can also give it examples of what we don't want it to generate, and ask it to avoid that.</p>
<p>Few Shot Prompting has a few benefits: it's quick to train, it's easy to understand, and it's portable between different models. At its core it's just a slightly longer prompt - you could fine tune a model to do the same thing, and that's totally reasonable in many cases, but Few-Shotting is way easier (and more portable).</p>
<p>NarratorAI builds on top of the excellent <a href="https://sdk.vercel.ai/docs/introduction">Vercel AI SDK</a>, which means you can configure it to use pretty much any LLM you like. By default, it'll use GPT 4o, so the only configuration you need to do is to set up your <code>OPENAI_API_KEY</code> environment variable (Vercel AI <a href="https://sdk.vercel.ai/docs/foundations/providers-and-models">supports a range of AI providers</a> beyond OpenAI).</p>
<h2>Generating content</h2>
<p>Right now I generate 2 types of content with NarratorAI:</p>
<ul>
<li><strong>Intros</strong> for <a href="/blog/tag/ai">tag</a> <a href="/blog/tag/ui">pages</a> and the <a href="/blog">blog home page</a></li>
<li><strong>Outros</strong> for the end of each post, telling you about related articles</li>
</ul>
<p>In order to generate the <strong>intros</strong>, I need to grab the most recent X articles on tag XYZ and pass them to Narrator along with a prompt telling it what I want it to do. For the <strong>outros</strong>, I need to do a similar thing, except I need to find the X articles that are the most related to the one I am generating for. That turns out to be pretty easy as I'm already <a href="/2024/9/12/read-next-ai-content-recommendations-node">using ReadNext to automatically generate the related articles list</a>.</p>
<p>Because there's a little logic involved in assembling the pieces that I need to send to the LLM for each task, and because I want to be able to generate a given piece of content from either the UI or a CLI, I created a <code>TaskFactory</code> class that does all the heavy lifting for me. Here's a simplified version of how I use it:</p>
<pre><code class="language-TaskFactory.ts">//create our reusable Narrator instance
export const narrator = new Narrator({
  outputFilename: (docId) => `${docId}.md`,
  outputDir: path.join(process.cwd(), "editorial"),
  examplesDir: path.join(process.cwd(), 'editorial', 'examples'),
});

export class TaskFactory {
  //returns a GenerationTask for a given docId
  jobForId(docId: string): GenerationTask {
    const [exampleKey, slug] = docId.split("/");
    const { publishedPosts } = this.posts;

    if (exampleKey === "post") {
      return this.postJob(publishedPosts.find((post) => post.slug === slug));
    } else if (exampleKey === "tag") {
      return this.tagJob({ tag: slug });
    }
  }

  //returns a GenerationTask for a post outro
  postJob(post): GenerationTask {
    //summaries of related articles
    const relatedArticles = post.related
      ?.map((slug) => this.posts.publishedPosts.find((p) => p.slug === slug))
      .map((post) => ({ post, summary: this.readNext.getSummaryById(post.slug) }));

    return {
      docId: `post/${post.slug}`,
      prompt: postReadNextPrompt(post, this.posts.getContent(post), relatedArticles),
      suffix: "Please reply with a 2 sentence suggestion for what the reader should read next.",
    };
  }

  //returns a GenerationTask for a tag intro
  tagJob({ tag }): GenerationTask {
    //the 10 most recent posts for a given tag
    const recentPosts = this.posts.publishedPosts
      .filter((post) => post.tags.includes(tag))
      .slice(0, 10)
      .map((post) => ({ post, summary: this.readNext.getSummaryById(post.slug) }));

    return {
      docId: `tag/${tag}`,
      prompt: tagIntroPrompt(tag, recentPosts),
    };
  }
}
</code></pre>
<p>All that does is give me a function called <code>jobForId</code> that I can pass a <code>docId</code> to, and it will return a <code>GenerationTask</code> object that I can pass to the <code>narrator.generate</code> function. The <code>GenerationTask</code> object contains the <code>prompt</code> that I want to send to the LLM, along with a unique <code>docId</code> that identifies the content that I want to generate, and an optional <code>suffix</code>.</p>
<p>Now I can just run a single line of code to generate the intro/outro for any given tag or post:</p>
<pre><code class="language-ts">await narrator.generate(factory.taskForId("tag/ai"));
</code></pre>
<p>The one thing I haven't shown you here is the <code>tagIntroPrompt</code> function that <code>TaskFactory</code> refers to. That's just a function that takes a tag and a list of recent posts and returns a prompt that tells the LLM what I want it to generate. Here's a slightly simplified version of that function (the <code>postReadNextPrompt</code> function is similar):</p>
<pre><code class="language-TaskFactory.ts">//it's just a string. A long string, but a string.
function tagIntroPrompt(tag: string, recentPosts: RecentPost[] = []) {
  return `
    These are summaries of the ${recentPosts.length} most recent posts on my blog for the tag "${tag}".
    The summaries have been specifically prepared for you so that you have the context you need to 
    a very brief 2 paragraph overview of what I've been writing about recently regarding this tag.
    Write the editorial in my own tone of voice, as if I were writing it myself.
    It should be around 100 words.
    
    *** There's actually more stuff here, but you get the idea ***
  
    Keep it humble and not too high-faluting. I'm a technical blogger, not a poet.
    
    Here are the summaries of the recent blog posts:
    
    ${recentPosts.map(({ post, summary }) => articleRenderer(post, summary)).join("\n\n")}
`;
}

//LLM-friendly string for a given post summary
const articleRenderer = (post, summary) => `
ARTICLE METADATA:
Article Title: ${post.title}
Article relative url: ${post.relativeLink}
Tags: ${post.tags.join(", ")}
Published: ${timeAgo.format(new Date(post.date))}
ARTICLE SUMMARY: ${summary}
`;
</code></pre>
<p>It's just returning a string, which is then passed in as the <code>prompt</code> to the <code>generate</code> function. With those pieces in place, generating all ~200 pieces of intro and outro content for the whole site is done with this simple script:</p>
<pre><code class="language-script/generate-narration.ts">//to expose the OPENAI_API_KEY
import * as dotenv from "dotenv";
dotenv.config();

import Posts from "@/lib/blog/Posts";
import { TaskFactory, narrator } from "@/lib/blog/TaskFactory";

async function main() {
  const taskFactory = new TaskFactory();
  const posts = new Posts();

  //generate post "read next" outros
  for (const post of posts.publishedPosts) {
    await narrator.generate(taskFactory.jobForId(`post/${post.slug}`)!, { save: true });
  }

  //generate the intro per tag (but only for tags with 3 or more posts)
  const tags = posts.getTagsWithCounts().filter(({ count }) => count >= 3);

  for (const tag of tags) {
    await narrator.generate(taskFactory.jobForId(`tag/${tag.tag}`)!, { save: true });
  }

  //generate the overall /blog intro
  await narrator.generate(taskFactory.jobForId("recent-posts")!);
}

main()
  .catch(console.error)
  .then(() => process.exit(0));
</code></pre>
<p>There's a bunch more documentation for this over on the <a href="https://github.com/edspencer/narrator-ai">GitHub page for NarratorAI</a>.</p>
<h2>Training the Narrator for better outcomes</h2>
<p>You can write the best prompt in the world, but that doesn't mean the model is going to understand it the same way you do. The best way to improve the quality of the content that Narrator generates is to train it by giving it examples of good and bad generations. You can do this in two ways:</p>
<h3>Training with the CLI</h3>
<p>It's pretty easy to set up a simple script that will train the Narrator for you. Here's a slightly simplified version of the script I use to train the Narrator for this site:</p>
<pre><code class="language-script/train-narrator.ts">//expose the OPENAI_API_KEY
import * as dotenv from "dotenv";
dotenv.config();

import { TaskFactory, narrator } from "@/lib/blog/TaskFactory";
import Posts from "@/lib/blog/Posts";

async function main() {
  const taskFactory = new TaskFactory();
  const posts = new Posts();

  //iterates over each published post, generating content and asking for my judgment
  for (const post of posts.publishedPosts) {
    await narrator.train(taskFactory.jobForId("post/" + post.slug));
  }
}

main()
  .catch(console.error)
  .then(() => process.exit(0));
</code></pre>
<p>This script will iterate over each published post on the site, passing each "What to Read Next" task to Narrator's <code>train</code> function, which will ask me to rate what it generated. I can skip to the next one, but if I give a good/bad rating, that feedback will be used to improve the next generation.</p>
<p>Under the covers, Narrator saves the content, the good/bad verdict and the optional reason you give in a YAML file. Each time the <code>generate</code> function is called, Narrator will select some of the good and bad examples that you've given it and pass them in to the LLM as part of the prompt. That's the Few-Shotting we talked about earlier.</p>
<h3>Training with the React component</h3>
<p>You've already seen this bit. The live demo above has more than just a regenerate button - it also has thumbs up and down buttons to train Narrator based on your feedback.</p>
<p>These thumbs up/down buttons are rendered by a component in the <code>@narrator-ai/react</code> library, and are connected to a couple of simple React Server Functions on the backend, which hand most of the work off to NarratorAI. That's configured via a React Context Provider that Narrator - ahem - provides you with:</p>
<pre><code class="language-providers/Narrator.tsx">import { createNarrator } from '@narrator-ai/react';
import { regenerateNarration, saveExample } from '../actions/narration';

const Narrator = createNarrator({
  actions: {
    saveExample,
    regenerateNarration,
  },
});

export default Narrator;
</code></pre>
<p>The only thing we configure that provider with is an <code>actions</code> object, which accepts <code>saveExample</code> and <code>regenerateNarration</code> functions. Providing these at the top level of the app means that we can place any number of Narration UI elements throughout the app and they'll all transparently support rating and regeneration.</p>
<p>As far as those Server Functions inside <code>actions/narration.ts</code> go, they're just a couple of simple functions that call the NarratorAI backend:</p>
<pre><code class="language-actions/narration.ts">"use server"
import { TaskFactory, narrator } from '@/lib/blog/TaskFactory';
import { createStreamableUI } from 'ai/rsc';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { Spinner } from '@narrator-ai/react';

//called whenever you click a thumbs up or down
export async function saveExample(example) {
  return await narrator.saveExample(example);
}

//this is all we have to do to support streaming MDX content,
//but this function could totally just return a string instead if streaming isn't your thing
export async function regenerateNarration(docId: string) {
  const editor = await TaskFactory.create();
  const ui = createStreamableUI(&#x3C;Spinner />);

  (async () => {
    const textStream = await narrator.generate(editor.jobForId(docId), { stream: true, save: true });
    let currentContent = '';

    for await (const delta of textStream) {
      currentContent += delta;
      ui.update(&#x3C;MDXRemote source={currentContent} />);
    }

    ui.done(&#x3C;MDXRemote source={currentContent} />);
  })();

  //Narrator knows how to handle Vercel AI text &#x26; UI streams as well as vanilla JS strings
  return ui.value;
}
</code></pre>
<p>So long as you import the <code>Narrator</code> provider you exported from <code>providers/Narrator.tsx</code> somewhere high up in your app's React component tree you'll be all set. Something like this (though you'll probably have some other stuff in your actual layout):</p>
<pre><code class="language-layout.tsx">import NarratorProvider from "./providers/Narrator";

export default function layout({ children }) {
  return &#x3C;NarratorProvider>{children}&#x3C;/NarratorProvider>;
}
</code></pre>
<p>Now the final thing to do is to actually render our Narration content in our app. Because I do this in a few different places, I made a simple wrapper component that I can reuse:</p>
<pre><code class="language-NarrationWrapper.tsx">import { Narration } from "@narrator-ai/react";
import NarrationMarkdown from "./NarrationMarkdown";

const sparkleText = "This summary was generated by AI using narrator-ai.&#x3C;br /> Click to learn more.";

export function NarrationWrapper({
  id,
  title
}: {
  id: string;
  title: string;
}) {
  return (
    &#x3C;Narration
      title={title}
      id={id}
      sparkleLink="/about/ai"
      sparkleText={sparkleText}

      //this is what lets me regenerate and rate the content in dev mode only
      showActions={process.env.NODE_ENV === "development"}
    >
      &#x3C;NarrationMarkdown id={id} />
    &#x3C;/Narration>
  );
}
</code></pre>
<p>Most of the heavy lifting is done by the <code>&#x3C;Narration></code> component, which is what gives you the regenerate, thumbs up and thumbs down buttons. Note that it doesn't render the actual content for you - it can't know how you want to render your content so you need to do that yourself. In my case I just have a simple <code>NarrationMarkdown</code> component that uses <code>next-mdx-remote</code> to render the content:</p>
<pre><code class="language-NarrationMarkdown.tsx">"use server";

import { narrator } from "@/lib/blog/TaskFactory";
import { MDXRemote } from "next-mdx-remote/rsc";

async function NarrationMarkdown({ id }) {
  const content = narrator.getNarration(id);

  if (!content) {
    return null;
  } else {
    return &#x3C;MDXRemote source={content} />;
  }
}

export default NarrationMarkdown;
</code></pre>
<p>And that's it. Now you can throw in as many <code>&#x3C;NarrationWrapper title="This is cool!" docId="tag/ai" /></code> components as you like throughout your app, and they'll all support regeneration and rating of the content. Here's precisely how that React snippet turns out, this time showing the intro content for the <a href="/blog/tag/ai">AI tag</a>:</p>
<p></p>
<p>Go on, click the regenerate button a few times. You earned it.</p>
<h2>Use it in your own project</h2>
<p>Anyway, that's it. It's fun and easy to use. There are more docs and examples over on the <a href="https://github.com/edspencer/narrator-ai">NarratorAI GitHub page</a>, and you can install it from NPM like this:</p>
<pre><code class="language-bash">npm install narrator-ai @narrator-ai/react
</code></pre>
<p>Godspeed and happy generating!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/NarratorAI-logo-wide.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ReadNext: AI Content Recommendations for Node JS]]></title>
            <link>https://edspencer.net//2024/9/12/read-next-ai-content-recommendations-node</link>
            <guid>read-next-ai-content-recommendations-node</guid>
            <pubDate>Thu, 12 Sep 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Recently I posted about <a href="/2024/9/11/ai-content-recommendations-typescript">AI Content Recommendations with TypeScript</a>, which concluded by introducing a new NPM package I've been working on called <a href="https://www.npmjs.com/package/read-next">ReadNext</a>. This post is dedicated to ReadNext, and will go into more detail about how to use ReadNext in Node JS, React, and other JavaScript projects.</p>
<h2>What it is</h2>
<p>ReadNext is a Node JS package that uses AI to generate content recommendations. It's designed to be easy to use, and can be integrated into any Node JS project with just a few lines of code. It is built on top of <a href="https://www.langchain.com/">LangChain</a>, and delegates to an LLM of your choice for summarizing your content to generate recommendations. It runs <strong>locally</strong>, does not require you to deploy anything, and has broad support for a variety of content types and LLM providers.</p>
<p>ReadNext is not an AI itself, nor does it want your money, your data or your soul. It's just a library that makes it easy to find related content for developers who use JavaScript as their daily driver. It's best used at build time, and can be integrated into your CI/CD pipeline to generate recommendations for your content as part of your build process.</p>
<h2>How to use it</h2>
<p>Get started in the normal way:</p>
<pre><code class="language-sh">npm install read-next
</code></pre>
<p>Configure a ReadNext instance:</p>
<pre><code class="language-typescript">import { ReadNext } from 'read-next'

const readNext = await ReadNext.create({
  cacheDir: path.join(__dirname, 'read-next'),
  parallel: 10
})
</code></pre>
<p>Index your content:</p>
<pre><code class="language-typescript">await readNext.index({
  sourceDocuments: [
    {
      pageContent: 'This is an article about React Server Components',
      id: 'rsc'
    },
    {
      pageContent: 'This is an article about React Hooks',
      id: 'hooks'
    },
    //... as many as you like
  ]
})
</code></pre>
<p>Generate recommendations:</p>
<pre><code class="language-typescript">const related = await readNext.suggest({
  sourceDocument: {id: 'rsc'},
  limit: 5
})
</code></pre>
<p>That's it! Under the covers, ReadNext creates embeddings for your content - after first running it through a summarization process - then stores the embeddings in a <a href="https://github.com/facebookresearch/faiss">FAISS</a> vector store. This allows it to keep a local cache of the work it has done, and to quickly generate recommendations for your content.</p>
<h2>Full Example usage in a React application</h2>
<p>I use ReadNext on this blog to generate related content recommendations for each post. It's a Next JS app, so I run ReadNext as part of the build process to generate recommendations for each post. The recommendations are stored in the frontmatter of each post (<a href="/2024/8/28/using-markdown-with-nextjs">I use .mdx files for the blog content</a>), and displayed at the bottom of each post.</p>
<p>It's also being used inside my <a href="https://rsc-examples.edspencer.net">RSC Examples</a> project, which is an <a href="https://github.com/edspencer/rsc-examples">open source collection of examples</a> of how to use React Server Components in various contexts. Each example has some explanatory text and code snippets, along with a live example, but even though that's not a traditional "article" per se, ReadNext is flexible enough to work with it.</p>
<p>Here's <a href="https://github.com/edspencer/rsc-examples/blob/1b00060e39cc27e0059751d89e4e48b7c89c87d3/src/script/related.tsx">the actual script</a> that RSC Examples uses to generate related examples for each example:</p>
<pre><code class="language-related.tsx">import path from 'path'
import { ReadNext } from 'read-next'

import Examples, { Example } from '../lib/examples'

const summarizationPrompt = `
The following content is a markdown document about an example of how to use React Server
Components. It contains sections of prose explaining what the example is about, may contain
links to other resources, and almost certainly contains code snippets.

Your goal is to generate a summary of the content that can be used to suggest related examples.
The summary will be used to create embeddings for a vector search. When you come across code
samples, please summarize the code in natural language.

Do not reply with anything except your summary of the example.`

const cacheDir = path.join(__dirname, '..', '..', 'read-next')
async function main() {
  // STEP 1 - create a ReadNext instance
  const readNext = await ReadNext.create({
    cacheDir,
    summarizationPrompt,
  })

  // STEP 2 - index all the examples
  const examples = new Examples()
  const { publishedExamples } = examples

  const sourceDocuments = publishedExamples.map((example: Example) => ({
    pageContent: examples.getContent(example),
    id: example.slug,
  }))

  await readNext.index({ sourceDocuments })

  // STEP 3 - generate related examples for each example
  for (const example of publishedExamples) {
    const {related} = await readNext.suggest({
      sourceDocument: sourceDocuments.find((s: any) => s.id === example.slug)!,
      limit: 5,
    })

    examples.updateMatter(example, {
      related: related.map(
        (suggestion: any) => suggestion.sourceDocumentId,
      ),
    })
  }
}

main()
  .catch(console.error)
  .then(() => process.exit(0))
</code></pre>
<p>The script just does 3 simple things:</p>
<ol>
<li>Creates a ReadNext instance</li>
<li>Indexes all the examples</li>
<li>Generates related examples for each example</li>
</ol>
<p>The <a href="https://github.com/edspencer/rsc-examples">RSC Examples project</a> stores its content as .mdx files, so the final part of the script is just calling a utility function to update the frontmatter on each example with the related examples that ReadNext generated.</p>
<p>The <code>summarizationPrompt</code> is optional but here we're taking advantage of it to better explain to the LLM that the content it is about to transform is a markdown document about an example of how to use React Server Components, not a long form article as it would usually expect. Here's the full thing:</p>
<p><a href="https://github.com/edspencer/rsc-examples/commit/1b00060e39cc27e0059751d89e4e48b7c89c87d3">Here's the actual commit</a> that was everything required to get ReadNext completely integrated with RSC Examples (<a href="https://github.com/edspencer/rsc-examples/commit/9797ac85bd7b756b2b1372ca28f7e6fff9d66fbd">the next commit</a> shows the output that ReadNext generated). There are a couple of simple UI components to display the recommendations, otherwise it's just one script that runs ReadNext to (re-)generate the recommendations.</p>
<h2>Where to use it</h2>
<p>I spend most of my time in React, usually within Next JS, and typically write TypeScript, so most of what I create is a union of those technologies. ReadNext is really a node project, and you don't need anything to do with React/Next/TypeScript to use it. But it does work really well with those technologies, because that's my stack and it would annoy me if it didn't.</p>
<p>The first time ReadNext runs, it needs to index all of the content you give it. Because this involves a summarization step, this can take a few seconds per article. Subsequent runs will be faster because ReadNext caches the summarizations and only regenerates them if it detects that an article's content has changed.</p>
<p>Every time a new article is added, or an existing article is updated, it's a good idea to re-run ReadNext as it's possible that your changes will alter the recommendations for one or more of your articles. Automating this as part of your build process is a good idea, and because ReadNext is a self-contained package it should be able to run pretty much anywhere.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/ReadNext-logo-wide.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[AI Content Recommendations with TypeScript]]></title>
            <link>https://edspencer.net//2024/9/11/ai-content-recommendations-typescript</link>
            <guid>ai-content-recommendations-typescript</guid>
            <pubDate>Wed, 11 Sep 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Part 3 will dive into how to use InformAI to create personalized predictive recommendations based on a user's pattern of interaction with your app.</p>
<p>In the last post, we used TypeScript to <a href="/2024/9/2/easy-rag-for-typescript-and-react-apps">create searchable embeddings for a corpus of text content and integrated it into a chat bot</a>. But chat bots are the tomato ketchup of AI - great as an accompaniment to something else, but not satisfying by themselves. Given that we now have the tools to vectorize our documents and perform semantic searches against them, let's extend that to generate content recommendations for our readers.</p>
<p>At the bottom of each of my blog articles are links to other posts that may be interesting to the reader based on the current article. The lo-fi way this was achieved was to find all the other posts which overlapped on one or more tags and pick the most recent one.</p>
<p>Quite often that works ok, but I'm sure you can think of ways it could pick a sub-optimal next article. Someone who knows the content well could probably pick better suggestions at least some of the time. LLMs are really well-suited to tasks like this, and should in theory have several advantages over human editors (such as not forgetting what I wrote last week).</p>
<p>We want to end up with some simple UI like this, with one or more suggestions for what to read next:</p>
<p>So how do we figure out which content to recommend based on what you're looking at?</p>
<h2>What's an article about? Summarizing vs Chunking</h2>
<p>We already <a href="/2024/9/2/easy-rag-for-typescript-and-react-apps">saw how we compare the "meaning" of a user question</a> (a string) with the "meaning" of our content (a bunch of other strings). This is basically the same problem - comparing the meaning of a text document to the meaning of N other text documents.</p>
<p>But we run into the same problem we ran into last time: the embedding model usually has a fairly low maximum token count, so you can't feed an entire article into it unless the article is below that boundary. In the previous article, we solved this by <a href="/2024/9/2/easy-rag-for-typescript-and-react-apps">chunking the document content into smaller sections</a>. But that wouldn't be a valid approach this time because we want to consider the whole article content, not just some chunk of it.</p>
<p>One answer to this is to pass our content through an automated summarization process first, guaranteeing that that article summary will be under the embedding model token limit while also preserving enough of the content to make sure that the suggestions are good ones.</p>
<p>There are several approaches to doing this, and I recommend Lan Chu's article on <a href="https://medium.com/the-data-perspectives/workarounds-openai-models-token-limit-issues-3ea52a60d937">working around LLM token limit issues</a> for a good overview of the options. At its simplest, though, we can do something like this:</p>
<pre><code class="language-summarize.ts">import OpenAI from 'openai';
const openai = new OpenAI();

async function summarize(content) {  
  const prompt = `Between the CONTENT_STARTS and CONTENT_ENDS will follow an article for you to summarize.
  The purpose of the summarization is to drive recommendations for what article somebody should read next,
  based on the article they are currently reading. The summarization should be as lengthy as necessary to
  capture the full essence of the article. It is not intended to be a short summary, but more of a 
  condensing of the article. All of the summarizations will be passed through an embedding model, 
  with the embeddings used to rank the articles.
  
  Please do not reply with any text other than the summary.
  
  CONTENT_STARTS
  ${content}
  CONTENT_ENDS`;
  
  const summary = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: prompt }],
    max_tokens: 2000,
    temperature: 0.7,
  });
  
  return summary.choices[0].message.content as string;
}

await summarize(post.getContent());
</code></pre>
<p>Now we can make an embedding for our article summary:</p>
<pre><code class="language-summarize.ts">export type Embedding = number[];

export async function generateEmbedding(input: string): Promise&#x3C;Embedding> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input,
  });

  return response.data[0].embedding as Embedding;
}

await generateEmbedding(summary);
</code></pre>
<p>This code has all of the error handling and other stuff stripped out make it easy to understand what's going on. In theory we now just need to iterate over all of our articles, generate the summaries, generate the embeddings, and then we can do a vector search to find the most similar articles to the one we're currently looking at.</p>
<h2>But that's expensive in both time and money</h2>
<p>There are about a hundred articles on this blog at the moment. To calculate the most relevant next article for each one, we therefore need to:</p>
<p>The $0.03 I can live with, but the 6 minutes is less cool. And that's just for 100 articles. If you have a larger corpus, you're going to be waiting a long time for this to finish. There are 2 obvious things to do to make that better:</p>
<ol>
<li><strong>Parallelize</strong>: do the summarization and vectorization in parallel</li>
<li><strong>Cache</strong>: cache the embeddings and the summaries so that we don't have to do them again</li>
</ol>
<p>Typically, once an article is written, it's not going to change (unless I made an embarrassing mistake and want to secretly fix it). So we can cache the summaries and their embeddings and only recompute them if the article changes. However, every time an article is published, it becomes a candidate for the best "Read Next" article for all of the other ones, so we have to perform the similarity search for all of the articles again.</p>
<h2>ReadNext does it for you</h2>
<p>This stuff is involved enough that I ended up writing a TypeScript library to make it easier to work with. It's called <a href="https://github.com/edspencer/read-next">ReadNext</a> and it's available on GitHub and NPM. It provides a simple API to index your articles and retrieve the most relevant ones based on a given article.</p>
<p>Under the covers it does all the things we just talked about - summarization, embedding and vector search. It uses <a href="https://github.com/facebookresearch/faiss">FAISS</a> for the vector search, which is a neat, mature open source project that does this kind of thing really well. It caches the summaries and embeddings onto the file system so that you don't have to do them again, and it parallelizes the summarization and vectorization to make it faster.</p>
<p>I'll go into more detail on how to use it in the next post, but for now, here's a quick example of how you might use it:</p>
<pre><code class="language-typescript">import { ReadNext } from 'read-next';

const readNext = await ReadNext.create({
  cacheDir: '/path/to/some/cache/dir'
});

//grab all of our articles, pass them into ReadNext in this format:
const sourceDocuments = articles.map((post: any) => ({
  pageContent: posts.getContent(post),
  id: post.slug,
}));

//this creates the summaries and embeddings for all of the articles
await readNext.index({ sourceDocuments });
</code></pre>
<p>Now we can generate the recommendations for a given article:</p>
<pre><code class="language-typescript">const suggestions = await readNext.suggest({
  sourceDocument: {
    id: 'introducing-inform-ai'
  }
  limit: 5,
});
</code></pre>
<p>Which will give us a response like this:</p>
<pre><code class="language-json">{
  "id": "introducing-inform-ai",
  "related": [
    {
      "sourceDocumentId": "easy-rag-for-typescript-and-react-apps",
      "score": 0.864181637763977
    },
    {
      "sourceDocumentId": "understanding-react-server-components-and-suspense",
      "score": 0.8758435249328613
    },
    {
      "sourceDocumentId": "teams-using-nextjs-vercel-advantage",
      "score": 0.8849999904632568
    },
    {
      "sourceDocumentId": "demystifying-openai-assistants-runs-threads-messages-files-and-tools",
      "score": 0.9038872718811035
    },
    {
      "sourceDocumentId": "using-server-actions-with-nextjs",
      "score": 0.9190686941146851
    }
  ]
}
</code></pre>
<p>Lower scores indicate closer relatedness. Once you have this data, you can use it to generate a list of links to suggest to the user. Because the scores won't change unless the content changes, you can cache the results and only recompute them when the content changes, so the whole thing is eminently cacheable and well-suited to static site generation.</p>
<h2>Does it actually work?</h2>
<p>The old method I was using was simply showing you the most recent article that had at least one tag in common with the current one. That was ok much of the time, but I have a bunch of <a href="/blog/tag/extjs">Ext JS posts from 2009</a> that were recommending recent articles on RAG, solely because the RAG articles are the most recent ones that are tagged with <a href="/blog/tag/ui">"ui"</a>. They're not really related at all.</p>
<p>To test it out I ran <a href="https://github.com/edspencer/read-next">ReadNext</a> to generate recommendations for each article, then compared the FAISS score for ReadNext's recommendation to the score for the most recent article with at least one tag in common. In only 4% of cases was the old method suggesting the same article as ReadNext; in the other 96% of cases ReadNext suggested an article it thought was more relevant.</p>
<p>Here's a chart showing the difference in scores between the old method and ReadNext. It's ordered by the difference in scores, so the articles on the left are the ones where ReadNext thought the old method was most wrong:</p>
<p>The purple bars are the scores for the recommended article from <a href="https://github.com/edspencer/read-next">ReadNext</a>. The green bars are the delta between the tag-based recommendation method and the contextual AI generated recommendation. The taller the purple bar, the less confident ReadNext is in its suggestion. The taller the green bar, the more wrong ReadNext thinks the old method was.</p>
<p>You can see by hovering over the bars to the left edge of the chart that the articles where ReadNext thinks its recommendations are much better than the old way tend to be ones where the old method was suggesting a recent article with a tag in common but completely unrelated content - like recommending a RAG article to read next after an article on the ancient Ext.ux.Printer plugin.</p>
<h2>Improving the suggestions</h2>
<p>Wherever you see a tall purple bar in the chart above, it means that <a href="https://github.com/edspencer/read-next">ReadNext</a> is not very confident in its suggestion. This is because the article in question is not very similar to any of the other articles in the corpus. Sometimes there are just no particularly similar articles to pick, so in those cases we could fall back to the old method of picking the most recent article with a tag in common.</p>
<p>Looking at the data in the chart above, we could set a threshold of 0.9 or 1 for the FAISS score, and if ReadNext can't find a suggestion above that threshold, we could fall back to the old method. This would give us the best of both worlds - the ability to suggest more relevant articles when they're available, but not to suggest something completely unrelated when there's nothing better to suggest.</p>
<p>If we wanted to prioritize newer content, we could apply a decay modifier to increase the candidate article's score by an amount proportional to the difference in publication date. This would make newer articles more likely to be recommended, but only when they're actually relevant.</p>
<p>We'll get a bit more into this in the next post, where I'll show you how to use <a href="https://github.com/edspencer/read-next">ReadNext</a> in your own projects, along with how I used it for the <a href="https://rsc-examples.edspencer.net">RSC Examples site</a> to generate related React Server Component examples.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/read-next-edspencer-content-chart.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Easy RAG for TypeScript and React Apps]]></title>
            <link>https://edspencer.net//2024/9/2/easy-rag-for-typescript-and-react-apps</link>
            <guid>easy-rag-for-typescript-and-react-apps</guid>
            <pubDate>Mon, 02 Sep 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>This is the first article in a trilogy that will go through the process of extracting content from a large text dataset - my blog in this case - and making it available to an LLM so that users can get answers to their questions without searching through lots of articles along the way.</p>
<p><strong>Part 1</strong> will cover how to process your text documents for easy consumption by an LLM, throw those embeddings into a vector database, and then use that to help answer the user's questions. There are a million articles about this using Python, but I'm principally a TypeScript developer so we'll focus on TS, React and NextJS.</p>
<p><strong><a href="/2024/9/11/ai-content-recommendations-typescript">Part 2</a></strong> covers how to make an AI-driven "What to Read Next" component, which looks at the content of an document (or blog post, in this case) and performs a semantic search through the rest of the content to rank which other posts are most related to this one, and suggest them.</p>
<p><strong>Part 3</strong> will extend this idea by using InformAI to track which articles the user has looked at and attempt to predictively generate suggested content for that user, personalizing the What to Read Next component while keeping the reader completely anonymous to the system.</p>
<h2>Let's RAG</h2>
<p>About a week ago I released <a href="https://github.com/edspencer/inform-ai">InformAI</a>, which allows you to easily surface the state of your application UI to an LLM in order to help it give more relevant responses to your user. In that intro post <a href="/2024/8/26/introducing-inform-ai">I threw InformAI into the blog post itself</a>, which gave me a sort of zero-effort poor man's RAG, as the LLM could see the entire post and allow people to ask questions about it.</p>
<p>That's not really what InformAI is intended for, but it's nice that it works. But what if we want to do this in a more scalable and coherent way? This blog has around 100 articles, often about similar topics. Sometimes, such as when I release open source projects like InformAI, it's one of the only sources of information on the internet about the given topic. You can't ask ChatGPT what InformAI is, but with a couple of tricks we can transparently give ChatGPT access to the answer so that it seems like it magically knows stuff it was never trained on.</p>
<p>In reality, having a chatbot that is able to answer questions about content from my blog is not likely to be super useful, but the process to achieve it is adaptable to many situations. It's likely that your company has hundreds or thousands of internal documents that contain answers to all kinds of questions, but finding that information can be difficult, and you may need to manually piece information together from a bunch of sources to get to the answer you need.</p>
<h2>What's RAG?</h2>
<p>Retrieval Augmented Generation refers to a group of techniques to intercept a user's message on its way to an LLM, look at what the user wrote, try to find relevant information from your own dataset about that question, and then pass that information along with the original question to the LLM, with the hope that the LLM will use it to give the user a good answer.</p>
<p>Concretely, what this usually boils down to is jamming a bunch of additional text content into the LLM prompt, usually labelling that as "Context", then passing the user query afterwards:</p>
<pre><code>Context:
1. InformAI makes it easy to surface all the information that you already have in your React components to an LLM or other AI agent...
2. InformAI keeps track of your component as it renders and re-renders in response to the user...

User Query: What is InformAI?
</code></pre>
<p>Ok so how do we actually achieve that? How do we pluck the relevant sections from a large corpus of text? Clearly, this requires 3 things:</p>
<ol>
<li>Understanding what the question is about</li>
<li>Understanding what the text in the source documents is about</li>
<li>Retrieving source documents that have semantic overlap with the question</li>
</ol>
<p>So how do we compute a semantic similarity score between two text strings? Well, first we turn the text into an array of numbers, called an <strong>embedding</strong>. This array is often referred to as a <em>vector</em>, because it is one, but it's also just an array of (usually floating point) numbers.</p>
<p>The "magic" of embedding creation is the process of turning a text string into that vector. I won't go into how that actually happens here, but for now it's enough to know that other people have done the hard work of making that possible, and that once we've produced embeddings for 2 strings, it's very easy to compare those two vectors and see how similar the strings are. This is the crux of what powers RAG - figuring out how similar strings are.</p>
<h2>Creating embeddings</h2>
<p>So how do we create this vector for a given string? We don't; we get an embedding model to do it for us. Most of the major LLM providers have an embedding API you can call with a text string and get a vector back. Here's how we do it with OpenAI, for example:</p>
<pre><code class="language-embedding.ts">import OpenAI from 'openai';
const openai = new OpenAI();

export type Embedding = number[];

export async function generateEmbedding(input: string): Promise&#x3C;Embedding> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input,
  });

  return response.data[0].embedding as Embedding;
}

generateEmbedding("What is InformAI?");

//returns an array of floats like this :
[0.013548319, 0.020720137, -0.0015670912, -0.018836489, 0.011183294, ...1536 elements in total]
</code></pre>
<p>Ok, that was pretty easy. In fact, we already achieved our first objective from above (Understanding what a question is about). Let's move on to the second objective - understanding what the text in the source documents is about.</p>
<h2>Processing our source documents</h2>
<p>We've got an easy way to turn strings of text into embedding vectors by calling an API, so we can just grab our source documents and do this for each of them, right? Well, kinda. It turns out that most embedding models have a strict limit on the length of a text string that you can pass in. In the case of the <code>text-embedding-ada-002</code> embedding model that we're using here, that limit is 8192 tokens.</p>
<p>Most of the articles here flirt with that limit, with maybe half of them being a little longer than the embedding model can handle. How do we handle this? By splitting the source documents up, of course. There are a bunch of ways you could split a text document, with pros and cons to each. If you want to get serious about it, using a library like <a href="https://js.langchain.com/v0.2/docs/tutorials/rag/">LangChain</a> is probably the way to go, as it has a bunch of strategies for sensibly chunking text documents.</p>
<p>But I didn't want to add a dependency to my app just to chunk text, so I just wrote a little function to chunk my text files instead. As I mentioned in the last post, <a href="/2024/8/28/using-markdown-with-nextjs">my blog uses MDX to blend Markdown and React components</a> for its content, so one decent strategy here is to just split the .mdx file (after removing the <a href="/2024/8/28/using-markdown-with-nextjs">frontmatter</a> of course) by heading:</p>
<pre><code class="language-embedding.ts">// Split the markdown into sections based on headings
function chunkMarkdownByHeaders(markdown: string): string[] {
  const chunks: string[] = [];
  const lines = markdown.split('\n');

  let currentChunk: string[] = [];

  lines.forEach(line => {
    // Check if the line is a header
    if (line.match(/^#{1,6}\s/)) {
      // If there's an existing chunk, add it to the chunks array
      if (currentChunk.length > 0) {
        chunks.push(currentChunk.join('\n'));
        currentChunk = [];
      }
    }

    // Add the line to the current chunk
    currentChunk.push(line);
  });

  // Add the last chunk if it exists
  if (currentChunk.length > 0) {
    chunks.push(currentChunk.join('\n'));
  }

  return chunks;
}

//returns an array of strings, one section per element
chunkMarkdownByHeaders("Your lovely long .mdx file here, replete with headings, subheadings and the like")
</code></pre>
<p>Technically, this function doesn't <em>guarantee</em> that our chunks are under the 8192 token limit, but in practice the chunks it generates are all substantially smaller than that limit. Again, for more robustness it's better to use something like LangChain for this.</p>
<p>Now that we've got our chunks, though, it's easy to generate embeddings for our entire text corpus:</p>
<pre><code class="language-embedding.ts">export type PostEmbedding = {
  _id: string;
  $vector: Embedding;
  content: string;
};

//generate a set of embeddings for an array of Posts
export async function generatePostEmbeddings(posts: Post[]): Promise&#x3C;PostEmbedding[]> {
  const embeddings: PostEmbedding[] = [];
  for (const post of posts) {
    embeddings.push(...(await generatePostSubEmbeddings(post, false)));
  }

  return embeddings;
}

//generate a set of embeddings for a single Post
export async function generatePostSubEmbeddings(post: Post): Promise&#x3C;PostEmbedding[]> {
  const { slug } = post;
  const content = await posts.getContent(post);

  console.log('Generating embedding for', slug);

  const chunks = chunkMarkdownByHeaders(content);
  const embeddings: PostEmbedding[] = [];
  for (const chunk of chunks) {
    embeddings.push({
      _id: `${slug}-${chunks.indexOf(chunk)}`,
      $vector: await generateEmbedding(chunk),
      content: chunk,
    });
  }

  console.log('Embeddings generated', embeddings.length);

  return embeddings;
}

//returns an array of PostEmbedding objects, each of which has a unique ID, a chunk of text content,
//and a $vector embedding for that content
generatePostEmbeddings(posts.findAll());
</code></pre>
<p>Cool - now we've solved objectives 1 &#x26; 2 - we've got vector embeddings for our entire text corpus, as well as an easy way to vectorize questions from the user. But right now it's all just a bunch of arrays in memory - what we need is a way to compare our user question vector with all the embeddings we made for our text content. We need a vector database.</p>
<h2>Using a Vector Database</h2>
<p>Vector Databases come in many forms. You can, of course, stuff vectors into pretty much any database - they're just arrays of numbers after all, but what we mean by "Vector Database" is one that makes it easy to pluck out vectors that are similar to one provided as our query (our vectorized/embedded user question, for example).</p>
<p>A bunch of vector-optimized databases have cropped up recently, but even traditional relational databases like Postgres and mysql are gaining vector capabilities, which may make things easier if you've already got one of those in your mix. In my case, there was no existing database behind this blog, so I decided to integrate with a relatively new player in the market - <a href="https://www.datastax.com/products/datastax-astra">Astra by DataStax</a>.</p>
<p>Astra is attractive for my use case because it's cloud hosted, allowing me to continue deploying my largely SSG blog application to Vercel without having to worry about orchestrating database deployments, migrations, or anything like that. It's far from the only option, but it's the one I took in this case. It's also free at my usage level, which is also cool.</p>
<p>DataStax provides a simple npm package called <a href="https://github.com/datastax/astra-db-ts">@datastax/astra-db-ts</a> that makes it pretty easy to interact with Astra. Under the covers Astra is built on top of <a href="https://cassandra.apache.org/_/cassandra-basics.html">Cassandra</a>, so it may be familiar already. I made a tiny <code>astra.ts</code> that exports a couple of functions like <code>getCollection</code> to make it easier for my <code>embedding.ts</code> file to interact with it:</p>
<pre><code class="language-astra.ts">import { DataAPIClient, SomeDoc } from '@datastax/astra-db-ts';

export function getCollection&#x3C;T extends SomeDoc>(name: string) {
  const db = getDb();

  return db.collection&#x3C;T>(name);
}

let client;

export function getClient(): DataAPIClient {
  if (!client) {
    client = new DataAPIClient(process.env.ASTRA_DB_TOKEN);
  }

  return client;
}

export function getDb() {
  return getClient().db(process.env.ASTRA_DB_API_ENDPOINT ?? '');
}
</code></pre>
<p>The only slight hitch I ran into was creating a token with the appropriate access - they have an RBAC system for tokens (which is good), but I had to create a token with more access than I expected to make it actually work (which is not so good). Anyway, to actually upload the embeddings we made before, we can just call <code>collection.insertMany</code>:</p>
<pre><code class="language-embedding.ts">export async function uploadEmbeddings(documents: PostEmbedding[]) {
  const collection = getCollection&#x3C;PostEmbedding>('posts');

  console.log('Connected to AstraDB:', collection);
  console.log(`Uploading ${documents.length} documents...`);

  try {
    const inserted = await collection.insertMany(documents);
    console.log(`* Inserted ${inserted.insertedCount} items.`);
  } catch (e) {
    console.log(e);
  }

  return collection;
}
</code></pre>
<p>That's it really. I skipped the bit about creating an account, database and collection within Astra, but I'm sure you can figure that out from their docs. Now all we have to do is grab our user message and fetch semantically similar text content from Astra.</p>
<h3>Putting it all together</h3>
<p>And here's how we can do that. This function just takes our user's question, turns it into an embedding, then searches our Astra database collection for similar embeddings using a vector search. Assuming we got some <code>searchResults</code> back, it will then just plop those into a prompt string along with the user's query, then send that along to OpenAI:</p>
<pre><code class="language-rag.ts">export async function ragUserMessage(input: string) {
  const inputEmbedding = await generateEmbedding(input);
  const collection = await getCollection&#x3C;PostEmbedding>('posts');
  const searchResults = await collection
    .find(
      {},
      {
        sort: { $vector: inputEmbedding },
        includeSimilarity: true,
        limit: 10,
      }
    )
    .toArray();

  // Format the context from search results
  const context = searchResults
    .map((result, index) => `${index + 1}. ${result._id}: ${result.content}`)
    .join('\n');

  const prompt = `Context:\n${context}\n\nUser Query:\n${input}`;

  // Send the prompt to the LLM
  const response = await openai.chat.completions.create({
    model: 'gpt-4', // Or the model you are using
    messages: [{ role: 'user', content: prompt }],
    max_tokens: 1000,
    temperature: 0.7,
  });

  return response.choices[0];
}
</code></pre>
<p>The response that we get back from the LLM should benefit from the context we found via semantic search, unless our results were not very good. Our <code>searchResults</code> array does include a similarity score for each result, so we can perform a cutoff or other processing to make sure we're only passing genuinely relevant content to the LLM. We can, of course, also modify the prompt itself to say things like "Using only the content provided here, please answer the user's question", or other text to try to constrain the LLM's response and limit its tendency to hallucinate. YMMV.</p>
<h2>Streaming is better</h2>
<p>Finally, that example above just used the basic OpenAI chat completion API, which will potentially sit there for a long time before showing you the entire LLM response in one go. That's a poor UX, so it's usually better to stream that text back. I'm a big fan of the Vercel AI SDK, and recently wrote about <a href="/2024/8/26/introducing-inform-ai">how to use that alongside the basic ChatWrapper React component in InformAI</a> to get a quick and dirty chatbox interface up and running.</p>
<pre><code class="language-ChatBot.tsx">"use client";

import { ChatWrapper } from "inform-ai";
import { useActions, useUIState } from "ai/rsc";

export function ChatBot({ className }: { className?: string }) {
  const { submitUserMessage } = useActions();
  const [messages, setMessages] = useUIState();

  return (
    &#x3C;ChatWrapper
      className={className}
      submitUserMessage={submitUserMessage}
      messages={messages}
      setMessages={setMessages}
      placeholder="Ask me anything about any content on edspencer.net"
    />
  );
}
</code></pre>
<p>None of this needs InformAI at all - I just happen to be using it already so I stuck with that, but you could equally roll your own chatbot UI, use <a href="https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat">useChat</a> from Vercel or find something else from off the shelf. Here's a live chatbot that you can ask any question about any article on this site (see the <a href="https://github.com/edspencer/inform-ai">InformAI README</a> for how to use this UI component):</p>
<p>Here's the actual code that I'm using on the back end to make this work. Most of this is taken directly from the <a href="https://github.com/edspencer/inform-ai">InformAI README</a> - I just added the <code>for..of</code> loop to replace your message to the LLM with the one returned from <code>prepareRAGMessage</code>:</p>
<pre><code class="language-app/actions/AI.tsx">'use server';

import { getMutableAIState, streamUI } from 'ai/rsc';
import { openai } from '@ai-sdk/openai';
import { Spinner } from '@/components/Spinner';

import { AssistantMessage } from 'inform-ai';
import { generateId } from 'ai';

import { ClientMessage } from '../providers/AI';
import { prepareRAGMessage } from '@/lib/embedding';

export async function submitUserMessage(messages: ClientMessage[]) {
  const aiState = getMutableAIState();

  // Do the RAG lookup
  for (const message of messages) {
    if (message.role === 'user') {
      message.content = await prepareRAGMessage(message.content as string);
    }
  }

  //add the new messages to the AI State so the user can refresh and not lose the context
  aiState.update({
    ...aiState.get(),
    messages: [...aiState.get().messages, ...messages],
  });

  //set up our streaming LLM response, with a couple of tools, a prompt and some onSegment logic
  //to add any tools and text responses from the LLM to the AI State
  const result = await streamUI({
    model: openai('gpt-4o-2024-08-06'),
    initial: &#x3C;Spinner />,
    system: `\
    You are a helpful assistant who helps people with questions about posts found at https://edspencer.net`,
    messages: [
      ...aiState.get().messages.map((message: any) => ({
        role: message.role,
        content: message.content,
        name: message.name,
      })),
    ],
    text: ({ content, done }) => {
      if (done) {
        aiState.update({
          ...aiState.get(),
          messages: [...aiState.get().messages, { role: 'assistant', content }],
        });

        aiState.done(aiState.get());
      }

      return &#x3C;AssistantMessage content={content} />;
    },
  });

  return {
    id: generateId(),
    content: result.value,
  };
}
</code></pre>
<p>And that's it - we've got LLM responses streaming into our React frontend that allow users to ask questions about things that ChatGPT was never trained on, but can give reasonable answers to anyway because we have RAG. In the next part we'll look at how to extend our use of RAG to generate more meaningful "Read Next" suggestions for our articles, before moving on to making intelligent, personalized suggestions based on reading history.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/rag-library.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Blending Markdown and React components in NextJS]]></title>
            <link>https://edspencer.net//2024/8/28/using-markdown-with-nextjs</link>
            <guid>using-markdown-with-nextjs</guid>
            <pubDate>Wed, 28 Aug 2024 12:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Authoring long-form content like blog posts is a pleasant experience with Markdown as it lets you focus on the content without worrying about the presentation or making the browser happy. Spamming <code>&#x3C;p></code> and <code>&#x3C;div></code> tags all over the place is a PITA and serves as a distraction from the content you're working on.</p>
<p>However, in a blog like this one, which deals with a lot of React/node/nextjs content, static text and images are limiting. We really want our React components to be live on the page with all of the richness and composability that React and JSX bring - so how do we blend the best of both of these worlds?</p>
<h2>MDX: Markdown plus React</h2>
<p><a href="https://mdxjs.com/">MDX</a> is an extension to Markdown that also allows you to import and use React components. It lets you write content like this:</p>
<pre><code class="language-mycontent.mdx">MDX is a blend of:

- normal markdown
- React components

&#x3C;Aside type="info">
  This blue box is an custom React component called `&#x3C;Aside>`, and it can be rendered by MDX along 
  with the other Markdown content.
&#x3C;/Aside>
</code></pre>
<p>That's rendering an <code>&#x3C;Aside></code> component, which is a simple React component I use in some of my posts and looks like this:</p>
<p>That's really cool, and we can basically use any React component(s) we like here. But first let's talk a little about metadata.</p>
<h2>Metadata matters</h2>
<p>A document is not just a document - it has a bunch of associated metadata like a publication status, a title, maybe some tags, timestamps, a summary, a canonical url, potentially author information and any number of other pieces of data that are not the document itself, but data about the document.</p>
<p>When it comes to things like text documents, metadata should be co-located with the content - ideally in the same file. The nature of metadata is usually quite different from text content, though - metadata typically has some structure to it and is well suited to a format like JSON or YAML.</p>
<p>Thankfully, markdown has a concept known as "Frontmatter", which is really just a yaml block shoved into the top of a markdown file. The frontmatter metadata for this very post looks like this:</p>
<pre><code class="language-yml">---
slug: using-markdown-with-nextjs
status: publish
title: Blending Markdown and React components in NextJS
tags:
  - nextjs
  - react
  - vercel
  - rsc
  - ui
  - mdx
date: '2024-08-28 06:31:02'
images:
  - /images/posts/mdx-content.png
description: >-
  Markdown is a really nice way to write content like blog posts, 
---
</code></pre>
<p>Unfortunately, while <a href="https://nextjs.org/docs/app/building-your-application/configuring/mdx">NextJS does have native support for markdown content</a>, it <a href="https://nextjs.org/docs/app/building-your-application/configuring/mdx#frontmatter">does not support frontmatter</a> out of the box. There may be instances where you don't really need any metadata, or can come up with some other way to handle it (I used to use a JSON file), but life is so much easier when you can use frontmatter.</p>
<p>Thankfully, it's pretty easy to do this using <a href="https://github.com/hashicorp/next-mdx-remote">MDXRemote</a>.</p>
<h2>Rendering MDX content</h2>
<p><a href="https://github.com/hashicorp/next-mdx-remote">MDXRemote</a> is a library that lets you render a string containing MDX content. That's useful if you are loading content from a database or something, but there's nothing stopping you from just reading file data and passing that in as a prop. Well, almost nothing - we've got to do something about that frontmatter first.</p>
<p>There are a few ways to do that - here's an approach I like using a library called <a href="https://github.com/jonschlinkert/gray-matter">gray-matter</a>:</p>
<pre><code class="language-tsx">import matter from 'gray-matter'

const source = fs.readFileSync(file)
const { data, content } = matter(source)

//this is now a JavaScript object of all the frontmatter yaml
console.log(data)

//this is the markdown content, not yet processed, but with the frontmatter stripped out
console.log(content)
</code></pre>
<p>Ok so we used <code>gray-matter</code> to process our MDX file into a JS object for the metadata, and a string for everything else, but that everything else - the content - is still Markdown. Let's turn it into HTML now using <code>&#x3C;MDXRemote></code>.</p>
<p>Here's the actual <a href="https://github.com/edspencer/rsc-examples/blob/main/src/components/MarkdownContent.tsx">MarkdownContent.tsx</a> that is used to power the <a href="https://rsc-examples.edspencer.net">RSC Examples</a>:</p>
<pre><code class="language-MarkdownContent.tsx">import remarkGfm from 'remark-gfm'
import { Code } from 'bright'
import { MDXRemote } from 'next-mdx-remote/rsc'
import { Callout } from './Callout'
import CaptionedContent from './CaptionedContent'
import Figure from './Figure'

Code.theme = {
  dark: 'github-dark',
  light: 'github-light',
}

Code.defaultProps = {
  lang: 'shell',
  theme: 'github-light',
}

const mdxOptions = {
  remarkPlugins: [remarkGfm], //adds support for tables
  rehypePlugins: [],
}

const components = {
  pre: Code,
  Callout,
  Figure,
  CaptionedContent,

  //just colors any `inline code stuff` blue
  code: (props: object) => (
    &#x3C;code style={{ color: 'rgb(0, 92, 197)' }} {...props} />
  ),
}

export default function MarkdownContent({ content }: { content: string }) {
  return (
    &#x3C;MDXRemote
      options={{ mdxOptions }}
      source={content}
      components={components}
    />
  )
}
</code></pre>
<p><code>MarkdownContent.tsx</code> shows off several of the capabilities of <code>MDXRemote</code>:</p>
<ul>
<li><strong>mdxOptions</strong> - allow us to pass in whatever remark/rehype plugins we like (I'm just using <a href="https://github.com/remarkjs/remark-gfm">remark-gfm</a> here to support Markdown tables)</li>
<li><strong>source</strong> - is just the source string we saw in the previous code block, passed in as a React prop</li>
<li><strong>components</strong> - allows us to render our custom React components like <a href="https://github.com/edspencer/rsc-examples/blob/main/src/components/Callout.tsx"><code>&#x3C;Callout></code></a> and <a href="https://github.com/edspencer/rsc-examples/blob/main/src/components/Figure.tsx"><code>&#x3C;Figure></code></a></li>
</ul>
<p>The <code>components</code> prop is where the interesting stuff is happening. By passing in <code>Callout</code>, <code>Figure</code> and <code>CaptionedContent</code> there - all of which are React components imported above - we can start putting content like <code>&#x3C;Callout type="warning">Be careful!&#x3C;/Callout></code> directly in our <code>.mdx</code> files (<code>Callout</code> is basically the same as the <code>Aside</code> component I use on the blog).</p>
<p>Here is also where we support syntax highlighting via the awesome <a href="https://bright.codehike.org/">Bright syntax highlighter</a>. That's what turns our code snippets (delineated by ```, which markdown turns into a <code>&#x3C;pre></code>) into beautifully syntax highlighted blocks of HTML. It's the same library I use for the syntax highlighting on this blog.</p>
<h2>Where to store the content</h2>
<p>You <em>could</em> store your MDX content anywhere, including inside a database, but generally it's easier to save them as files in your git repo. Not only is this one less dependency, but you get all the things like file histories, branching and reversion for free.</p>
<p>For the <a href="https://rsc-examples.edspencer.net/">RSC Examples app</a>, I wanted people to be able to get value by browsing the repo as much as by browsing the app itself. Most of the examples are just an .mdx file and a .tsx file - one explaining the example, the other executing it. By structuring things this way, you can grok an <a href="https://github.com/edspencer/rsc-examples/tree/main/src/app/examples/pages/slow/no-suspense">example like this one directly in the repo</a> almost as well as you can by <a href="https://rsc-examples.edspencer.net/pages/slow/no-suspense">playing with the live example itself</a>.</p>
<h3>A non-database database</h3>
<p>Keeping the content in source control is great, but you probably want to have things like index pages that list out the content, some kind of search, filtering by tag, or other dynamic functionality that requires your app to have some kind of database of all of your .mdx content.</p>
<p>In previous iterations of my blog app I kept a JSON file that acted as a sort of manifest of all of the posts I had written. It was annoying to have to keep switching from the .md file to the .json file to add metadata, but it did provide a "database" of all the content on the site.</p>
<p>Once I migrated to .mdx I was able to write a really piece of code that would just find all the .mdx files nested in some directory, parse the frontmatter using <code>gray-matter</code> and expose a couple of utility functions like getting the content, ready to be passed into <code>&#x3C;MarkdownContent></code>.</p>
<p>I do basically the exact same thing with this <a href="https://github.com/edspencer/rsc-examples/blob/main/src/lib/examples.ts">simple <code>Examples</code> class</a> inside the RSC Examples repo. If you are planning on having tens of thousands of .mdx files then you'd probably want to consider a different approach, but this ~100 lines of TypeScript makes it easy to do everything I need without adding the dependency of a database while keeping builds fast and tooling unnecessary.</p>
<p>The <a href="https://github.com/edspencer/rsc-examples/blob/main/src/app/%5B...slug%5D/page.tsx">page.tsx</a> inside RSC Examples that actually renders the content now becomes pretty simple:</p>
<pre><code class="language-page.tsx">export default async function Page({ params }: Props) {
  const slug = params.slug.join('/')
  const examples = new Examples()
  const example = examples.publishedExamples.find(
    (example: any) => example.slug === slug,
  )

  if (!example) {
    return notFound()
  }

  const content = examples.getContent(example)

  return (
    &#x3C;DocsLayout frontmatter={example}>
      &#x3C;MarkdownContent content={content} />
    &#x3C;/DocsLayout>
  )
}
</code></pre>
<h3>Server-side rendering friendliness</h3>
<p>Both my blog and the <a href="https://rsc-examples.edspencer.net">RSC Examples site</a> are Next JS applications, hosted on the Vercel platform, and making heavy (almost exclusive) use of static server-side rendering. That's why both sites are generally pretty instantaneous to load - most of the work was done at deploy time, while still allowing for interactivity where it's needed.</p>
<p>Looking at our <a href="https://github.com/edspencer/rsc-examples/blob/main/src/app/%5B...slug%5D/page.tsx">page.tsx</a> again, the <code>generateStaticParams</code> function is super easy to implement, and allows Next JS to build all of our Examples as static content at deploy time:</p>
<pre><code class="language-page.tsx">export function generateStaticParams() {
  const { publishedExamples } = new Examples()

  const all = publishedExamples.map((example: any) => ({
    slug: example.slug.split('/'),
  }))

  return all
}
</code></pre>
<p>Not only does this make our content cheap and easy to host, it makes it blazing fast too, while still supporting as much interactivity as we need. Even though the .mdx files are rendered server-side, we can still render rich, interactive React components that run on the client side, like this little <a href="/2024/8/26/introducing-inform-ai">InformAI</a>-driven chatbot that's running live inside this page and lets you ask questions about this post:</p>
<p>That's a live client-side component rendered inside a .mdx file at build time inside a static, server-rendered page. It's awesome how well this all works together. To find out more, take a poke around the <a href="https://github.com/edspencer/rsc-examples">RSC Example GitHub repo</a> or ping me on <a href="https://x.com/edspencer">twitter</a>.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/mdx-content.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing InformAI - Easy & Useful AI for React apps]]></title>
            <link>https://edspencer.net//2024/8/26/introducing-inform-ai</link>
            <guid>introducing-inform-ai</guid>
            <pubDate>Mon, 26 Aug 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Most web applications can benefit from AI features, but adding AI to an existing application can be a daunting prospect. Even a moderate-sized React application can have hundreds of components, spread across dozens of pages. Sure, it's easy to tack a chat bot in the bottom corner, but it won't be useful unless you integrate it with your app's contents.</p>
<p>This is where <a href="https://github.com/edspencer/inform-ai">InformAI</a> comes in. InformAI makes it easy to surface all the information that you already have in your React components to an LLM or other AI agent. With a few lines of React code, your LLM can now see exactly what your user sees, without having to train any models, implement RAG, or any other expensive setup.</p>
<p>InformAI is not an AI itself, it just lets you expose components and UI events via the simple <code>&#x3C;InformAI /></code> component. Here's how we might add AI support to a React component that shows a table of a company's firewalls:</p>
<pre><code class="language-tsx">&#x3C;InformAI
  name = "Firewalls Table"
  prompt = "Shows the user a paginated table of firewalls and their scheduled backup configurations"
  props = {{data, page, perPage}}
/>
</code></pre>
<p>Under the covers, InformAI keeps track of your component as it renders and re-renders in response to the user. When the user is ready to ask the LLM a question, InformAI automatically wraps up all of that context and renders it into an LLM-friendly data format that gets sent along with the user's message to an LLM backend of your choice.</p>
<p>When your user next sends the AI a question, all of that LLM-friendly component state is sent to the LLM along with the user's input, allowing the LLM to respond based on what the user can see. Because InformAI also supports React Server Components, the LLM can also respond with rendered, specifically-configured components as well as a traditional text reply, allowing conversations like this:</p>
<p>Here the user is able to ask the LLM questions, and it's answering based partly on the information exposed to it via <a href="https://github.com/edspencer/inform-ai">InformAI</a>. In the example above, the LLM responded to the second message by returning a <code>&#x3C;BackupsTable /></code> component, rendered server-side and streamed to the client just as if it were text. There's a <a href="https://github.com/edspencer/inform-ai">full walkthrough</a> on how to do with alongside the <a href="https://sdk.vercel.ai/docs/introduction">Vercel AI SDK</a> on the <a href="https://github.com/edspencer/inform-ai/blob/main/README.md">InformAI README</a>.</p>
<h2>Live Demo</h2>
<p>InformAI is easy to add to any React app, which includes this blog, which is a NextJS app that happens to use a lot of React Server Components. Because InformAI works just as well with <a href="/blog/tag/rsc">RSC</a> as it does with traditional client-side React components, I was able to add LLM-awareness to every post on my site by just adding this one <code>&#x3C;InformAI /></code> tag:</p>
<pre><code class="language-tsx">import { InformAI, InformAIProvider } from 'inform-ai';

//a react server component that renders a single blog post
export async function PostContent({ post }: { post: Post }) {
  const postFilePath = pathForPostFile(post);
  const source = fs.readFileSync(postFilePath);
  const { content } = matter(source);

  //in reality, the &#x3C;InformAIProvider> is in my layout.tsx, but I show it here instead for clarity
  return (
    &#x3C;InformAIProvider>
      &#x3C;InformAI
        name="Blog Post Content"
        props={{ content, post }}
        prompt="Shows the blog content to the user. Also gives you the full post metadata"
      />
      &#x3C;MarkdownContent content={content} />
    &#x3C;/InformAIProvider>
  );
}
</code></pre>
<p>You can pass whatever <code>props</code> you like to InformAI - in this case I'm passing the whole <code>post</code> object as well as the markdown content for the post. InformAI ships with a couple of UI components that make development easier, such as the <code>&#x3C;CurrentState /></code> component that you can drop anywhere in your app and see what InformAI sees:</p>
<p>That's a live component - expand a row to see the <code>name</code>, <code>props</code> (<code>post</code> and <code>content</code>) and <code>prompt</code> that we supplied in our template. Now that we've told InformAI about our component, we can integrate a chat bot like this one and are immediately able to talk to the LLM, knowing that it sees what we see:</p>
<p>Try asking it a question about something in this article (e.g. "what is InformAI?") and you'll get a response from the LLM that incorporates all of the context that all of your InformAI-integrated components have published.</p>
<p>Although <a href="https://github.com/edspencer/inform-ai">InformAI</a>'s focus is on collecting intelligence from your components and exposing that to an LLM, it does also ship with a couple of basic UI components to help you get started quickly. These UI components are totally optional and you'll probably want to roll your own at some point, but adding the ChatBot above to your app can be as simple as this:</p>
<pre><code class="language-ChatBot.tsx">"use client";

import { ChatWrapper } from "inform-ai";
import { useActions, useUIState } from "ai/rsc";

export function ChatBot({ className }: { className?: string }) {
  const { submitUserMessage } = useActions();
  const [messages, setMessages] = useUIState();

  return (
    &#x3C;ChatWrapper
      className={className}
      submitUserMessage={submitUserMessage}
      messages={messages}
      setMessages={setMessages}
    />
  );
}
</code></pre>
<p>See the <a href="https://github.com/edspencer/inform-ai/blob/main/README.md">InformAI README</a> for a little more on how to do that, including how to set up the <code>submitUserMessage</code> function.</p>
<h2>What it's Good For</h2>
<p><a href="https://github.com/edspencer/inform-ai">InformAI</a> allows you to rapidly and iteratively adopt deep AI integration into your new or existing React applications. Although it's generally just a couple of lines per component, it can provide value to your users even if you don't integrate your entire app with it in one go. Gradual adoption is easy, and it works across server and client-side components.</p>
<p>With a few lines of code the LLM can see everything your user can, including a timeline of events as the user navigates around the app. Imagine a cyber security app that tracks viruses blocked by your firewall - usually there is a lot of noise in the signal there and it can take several steps to investigate some pattern you might see in the data - with InformAI your components can emit events as your user performs their investigation, as well as information about any streamed UI components it sent in response to questions. With all of this context the AI can understand the journey that the user is embarking on, and preemptively render custom UI or fetch data to help complete the task.</p>
<p>InformAI is not a silver bullet: any content that is not on the screen already (or at least accessible to your React components) obviously won't be surfaced to the AI this way. Your app's AI will still benefit from RAG, model fine-tuning and tool provision and InformAI is not a substitute for any of those, but for React developers who may not already be deep in the weeds with LLM fine-tuning, InformAI provides a great bang for the buck in getting started.</p>
<h3>Play with it online</h3>
<p>I have a little open source nextjs app called <a href="https://lansaver.edspencer.net/">LANsaver</a>, which is a simple app that helps back up network devices like firewalls, managed switches and Home Assistant instances in case something dies and you need to restore it. I integrated <a href="https://github.com/edspencer/inform-ai">InformAI</a> into it and you can see an online demo version at https://lansaver.edspencer.net. I left the <code>&#x3C;CurrentState /></code> component in place there so you can see what is being exposed to the LLM via InformAI.</p>
<p>LANsaver (<a href="https://github.com/edspencer/lansaver">see source on github</a>) is pretty basic - all it really knows about are Devices, Backups and Schedules, but hopefully it provides a way to see how easy it is to integrate <a href="https://github.com/edspencer/inform-ai">InformAI</a> into your own applications. LLMs thrive on data, so apps with more meat on the bones than LANsaver will benefit more from <a href="https://github.com/edspencer/inform-ai">InformAI</a>.</p>
<h2>Next Steps</h2>
<p><a href="https://github.com/edspencer/inform-ai">InformAI</a> is fairly new but has a decent amount of documentation and automated testing. Its API is relatively small and stable, and it is <a href="https://www.npmjs.com/package/inform-ai">published as an npm module called inform-ai</a>. Some near-term improvements include providing an easy way to tell the LLM that the user navigated to a new page (e.g. components may no longer be visible), a proper docs site and some more examples.</p>
<p>If you're interested in this stuff and haven't had the chance to check out the <a href="https://sdk.vercel.ai/docs/introduction">Vercel AI SDK</a> yet, I highly recommend you do. Beyond being useful and easy to use, some of the source code is just beautiful (I spent a couple of days just admiring the streaming code recently). It's well worth following <a href="https://twitter.com/lgrammel">Lars Grammel</a> to get updates first-hand as he seems to put out updates pretty much daily.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/inform-ai-magic-square.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Error handling and retry with React Server Components]]></title>
            <link>https://edspencer.net//2024/7/16/errors-and-retry-with-react-server-components</link>
            <guid>errors-and-retry-with-react-server-components</guid>
            <pubDate>Tue, 16 Jul 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="/blog/tag/rsc">React Server Components</a> are a game-changer when it comes to building large web applications without sending megabytes of JavaScript to the client. They allow you to render components on the server and stream them to the client, which can significantly improve the performance of your application.</p>
<p>However, React Server Components can throw errors, just like regular React components. In this article, we'll explore how to handle and recover from errors in React Server Components.</p>
<h2>Error boundaries</h2>
<p>In React, you can use error boundaries to catch errors that occur during rendering, in lifecycle methods, or in constructors of the whole tree below them. An error boundary is a React component that catches JavaScript errors anywhere in its child component tree and logs those errors, displaying a fallback UI instead of crashing the entire application.</p>
<p>To create an error boundary in React, you need to define a component that implements the <code>componentDidCatch</code> lifecycle method. This method is called whenever an error occurs in the component tree below the error boundary.</p>
<p>Here's an example of an error boundary component:</p>
<pre><code class="language-ErrorBoundary.tsx">class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({ hasError: true });
    console.error(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return &#x3C;div>Something went wrong.&#x3C;/div>;
    }

    return this.props.children;
  }
}
</code></pre>
<p>Alternatively, you can use the <code>ErrorBoundary</code> component from the <a href="https://www.npmjs.com/package/react-error-boundary">react-error-boundary</a> library, which provides a slightly more robust implementation of error boundaries, including support for error recovery and retrying rendering. Here's how we might use that on an RSC-rendered page:</p>
<pre><code class="language-page.tsx">'use client'
import { ErrorBoundary } from 'react-error-boundary'

export default function PageWithBoundary() {
  return (
    &#x3C;>
      &#x3C;p>
        This page demonstrates what happens when an error is thrown in a
        component with an explicit error boundary.
      &#x3C;/p>

      &#x3C;ErrorBoundary fallback={&#x3C;ErrorFallback />}>
        &#x3C;ErrorComponent />
      &#x3C;/ErrorBoundary>
    &#x3C;/>
  )
}

function ErrorComponent() {
  throw new Error('Error thrown in component')

  return 'This will never be rendered'
}

function ErrorFallback() {
  return (
    &#x3C;div className="text-red-700">There was an error with this content&#x3C;/div>
  )
}
</code></pre>
<p>When we render this page, we'll end up seeing something like this:</p>
<p>This is useful as it allows the rest of our page to render and be usable, even if a component or two throw errors. It allows us to inform the user that something went wrong, at which point they're likely to want to hit the refresh button. But there's a better way...</p>
<h3>Retrying rendering</h3>
<p>Wouldn't it be cool if we could allow the user to retry rendering the component that errored out? With React Server Components, we can! Kind of.</p>
<p>Our ideal solution here would be to allow the rest of the page to render and be interactive, while the errored component is replaced with a button that allows the user to retry rendering it. If we're going to show the user an error, it's best not to take down the whole page with it, and to give them an easy way to recover from it.</p>
<p>Let's see how we might implement this:</p>
<pre><code class="language-page.tsx">import { ErrorBoundary } from 'react-error-boundary'
import ErrorFallback from './ErrorFallback'

export default function ResettablePage() {
  return (
    &#x3C;>
      &#x3C;p>
        This page has a component with a 50% chance of throwing an error. If it
        does, a Reset button will appear that you can click to reset the
        component.
      &#x3C;/p>
      &#x3C;p>
        This is useful for when you want to give the user a way to recover from
        an error without having to refresh the entire page. Refresh the page a
        few times if you don't get the error immediately.
      &#x3C;/p>

      &#x3C;ErrorBoundary FallbackComponent={ErrorFallback}>
        &#x3C;ErrorComponent />
      &#x3C;/ErrorBoundary>
    &#x3C;/>
  )
}

async function ErrorComponent() {
  // Simulate a delay so we can see the Reset button spinning
  await new Promise((resolve) => setTimeout(resolve, 1000))

  if (Math.random() > 0.5) {
    throw new Error('Error thrown in component')
  }

  return (
    &#x3C;p className="border border-blue-700 p-4">
      This has a 50% chance of throwing an error, but this time it rendered
      fine.
    &#x3C;/p>
  )
}

</code></pre>
<p>Ok so we have have a page that contains a bunch of content, plus a component that has a 50% chance of throwing an error. If it does, we'll show a Reset button that the user can click to retry rendering the component.</p>
<p>We used the <code>ErrorBoundary</code> component from <a href="https://www.npmjs.com/package/react-error-boundary">react-error-boundary</a> to catch the error and display the <code>ErrorFallback</code> component when an error occurs. The <code>ErrorFallback</code> component contains a button that allows the user to retry rendering the component. Here's what the <code>ErrorFallback</code> component looks like:</p>
<pre><code class="language-ErrorFallback.tsx">'use client'

import { startTransition, useState } from 'react'
import { useRouter } from 'next/navigation'

import Spinner from './Spinner'

export default function ErrorFallback({
  error,
  resetErrorBoundary,
}: {
  error: Error
  resetErrorBoundary: () => void
}) {
  const router = useRouter()

  //tracks the state of our reset button
  const [isResetting, setIsResetting] = useState(false)

  function retry() {
    setIsResetting(true)

    startTransition(() => {
      router.refresh()
      resetErrorBoundary()
      setIsResetting(false)
    })
  }

  return (
    &#x3C;div className="border border-orange-700 p-4 text-orange-700">
      &#x3C;p className="m-0 mb-2 p-0">There was an error loading this component&#x3C;/p>
      &#x3C;button
        onClick={() => retry()}
        disabled={isResetting}
        className="button inline-flex items-center gap-4 rounded-md border bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
      >
        {isResetting ? &#x3C;Spinner /> : null}
        Retry
      &#x3C;/button>
    &#x3C;/div>
  )
}
</code></pre>
<p>A few things to note here:</p>
<ul>
<li>The <code>ErrorFallback</code> component is a client component (so is its parent - the <code>ErrorBoundary</code> component that we used)</li>
<li>We use <code>router.refresh()</code> to retry the rendering of the component that errored out. This actually <strong>re-renders the whole page</strong>, but to the user it looks like only the errored component is being re-rendered</li>
<li>We need to wrap the <code>router.refresh()</code> call in the new <code>startTransition</code> API because <code>router.refresh()</code> is a long-running operation that does not return a Promise, so we can't <code>await</code> it</li>
<li>We used an <code>isResetting</code> state variable to allow us to show a spinner while the component is being re-rendered</li>
</ul>
<p>When we render this page, we'll see something like this:</p>
<p>That's a fully interactive iframe pointing to a live example on my RSC Examples site. You had a 50% chance of seeing an error, but you can hit the little refresh icon above the iframe if you got lucky/unlucky enough not to see an error.</p>
<p>Now, when you click the blue <code>Retry</code> button, our <code>retry</code> function within the <code>ErrorFallback</code> component will be called. This will set the <code>isResetting</code> state to <code>true</code>, refresh the page, reset the error boundary, and then set <code>isResetting</code> back to <code>false</code>. This will cause the <code>ErrorComponent</code> to be re-rendered, and with a bit of luck, it won't throw an error this time.</p>
<p>What actually happened here is that we reloaded the whole page, so it's not as surgical as it looks (the hint is in the <code>router.refresh()</code> call...). However, from the user's perspective, it feels very much like just this one single component is being retried, and existing state such as form input is maintained. This is significantly better than either crashing the whole page or forcing the user to refresh the whole page.</p>
<h2>Conclusion</h2>
<p>The combination of error boundaries and retrying rendering with React Server Components allows you to build robust web applications that can recover from errors gracefully. By catching errors and displaying a fallback UI, you can prevent your application from crashing and provide a better user experience.</p>
<p>Also, check out this <a href="https://www.youtube.com/watch?v=idEL0dv2V1A">excellent YouTube video</a> by Ryan Toronto on the same subject.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/rsc-error-component-boundary.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Promises across the void: Streaming data with RSC]]></title>
            <link>https://edspencer.net//2024/7/12/promises-across-the-void-react-server-components</link>
            <guid>promises-across-the-void-react-server-components</guid>
            <pubDate>Fri, 12 Jul 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>Last week we looked at <a href="/2024/7/1/decoding-react-server-component-payloads">how React Server Component Payloads work</a> under the covers. Towards the end of that article I mentioned a fascinating thing that you can do with RSC: sending <strong>unresolved promises</strong> from the server to the client. When I first read that I thought it was a documentation bug, but it's actually quite real (though with some limitations).</p>
<p>Here's a simple example of sending a promise from the server to the client. First, here's our server-rendered component, called SuspensePage in this case:</p>
<pre><code class="language-page.tsx">import { Suspense } from "react";
import Table from "./table";
import { getData } from "./data";

export default function SuspensePage() {
  return (
    &#x3C;div>
      &#x3C;h1>Server Component&#x3C;/h1>
      &#x3C;Suspense fallback={&#x3C;div>Loading...&#x3C;/div>}>
        &#x3C;Table dataPromise={getData(1000)} />
      &#x3C;/Suspense>
    &#x3C;/div>
  );
}
</code></pre>
<p>So we just imported a <code>getData()</code> function that returns a promise that resolves after 1 second. This simulates a call to a database or other asynchronous action. Here's our fake <code>getData()</code> function:</p>
<pre><code class="language-data.tsx">const fakeData = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
]

export async function getData(delay: number): Promise&#x3C;any> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(fakeData)
    }, delay)
  })
}
</code></pre>
<p>We pass that promise to a component called <code>Table</code> as a prop called <code>dataPromise</code>. Here's that component (note the <code>use client</code> directive, which tells the compiler that this component will be run on the client):</p>
<pre><code class="language-table.tsx">"use client";
import { use } from "react";

export default function Table({ dataPromise }: { dataPromise: Promise&#x3C;any> }) {
  const data = use(dataPromise)

  return (
    &#x3C;table className="max-w-5xl table-auto text-left">
      &#x3C;thead>
        &#x3C;tr>
          &#x3C;th>ID&#x3C;/th>
          &#x3C;th>Name&#x3C;/th>
        &#x3C;/tr>
      &#x3C;/thead>
      &#x3C;tbody>
        {data.map((row: any) => (
          &#x3C;tr key={row.id}>
            &#x3C;td>{row.id}&#x3C;/td>
            &#x3C;td>{row.name}&#x3C;/td>
          &#x3C;/tr>
        ))}
      &#x3C;/tbody>
    &#x3C;/table>
  )
}
</code></pre>
<p><code>dataPromise</code> is not a good name for a prop in reality, but I call it that here to make it clear that this is a Promise, not the data itself. We don't get the actual data until that Promise resolves.</p>
<p>Note that although React Server Components can be async functions, we're not actually writing our server-rendered <code>SuspensePage</code> component using async, nor are we making the client-side <code>Table</code> function an async one (that's partially because async components are not yet supported on the client side, but also partly because we don't need to).</p>
<h3>Async/await-ish via the power of <code>use()</code></h3>
<p>This component uses the new React <a href="https://react.dev/reference/react/use"><code>use</code></a> hook to wait for the promise to resolve. <a href="https://react.dev/reference/react/use"><code>use</code></a> accepts a Promise as an argument, and does some clever things:</p>
<ul>
<li>If the promise is already resolved, it returns the resolved value.</li>
<li>If the promise is not resolved, it suspends the component and waits for the promise to resolve.</li>
<li>If the promise rejects, it throws an error.</li>
</ul>
<p>Under the covers, in that second scenario (promise not yet resolved), <code>use</code> will actually <code>throw</code> the Promise, which is caught by React and used to suspend the component. This is how React knows to wait for the promise to resolve before rendering the component. That thrown Promise will be caught by the nearest <code>Suspense</code> boundary, which will then show the fallback until the Promise resolves.</p>
<p>When the Promise does eventually resolve, React will re-render the component with the resolved value. If we used <code>use()</code> multiple times, the pattern will repeat until all of the Promises have resolved and all of the components rendered (or until some of the Promises reject and the nearest Error Boundary renders).</p>
<p>Now, with <code>use</code>, React can suspend rendering of a component until a Promise resolves, which is a huge step forward in terms of simplifying asynchronous rendering. It does mean that any work done up until that point will be thrown away if the component suspends, but unless your Component is doing lots of heavy processing (which it should not be), this is not a big deal.</p>
<h3>How can I start a Promise on the server and have it resolve on the client?</h3>
<p>The key to sending a Promise from the server to the client is to <strong>not await it</strong> on the server. If you await the Promise on the server, you'll be sending the resolved value to the client, not the Promise itself, but the Promise may take some time to resolve, during which UI rendering is blocked and your user left waiting.</p>
<p>As for how this actually works under the covers, <a href="/2024/7/1/decoding-react-server-component-payloads">take a look at my post on how React Server Component Payloads work</a>, but the high level flow goes like this:</p>
<ol>
<li>The server renders the component tree, and notices that an unresolved Promise is being passed as a prop to a client-side component.</li>
<li>The server assigns an internal ID to that Promise, and sends that ID to the client in place of the Promise itself.</li>
<li>The client renders the component tree, and notices that a Promise ID is being passed as a prop to a client-side component. This suspends render until:</li>
<li>When the Promise resolves on the server-side, the server renders an inline <code>&#x3C;script></code> tag with the resolved value, keyed on the Promise ID it generated.</li>
</ol>
<p>The key to all this working is that the <em>server is streaming the HTML response</em> to the client, and doesn't actually close that stream until it has finished rendering everything, including resolved Promises. So in our case above, the server would likely render our very basic <code>SuspensePage</code> component in a few milliseconds, but then keep the stream open for another second while it waits for the <code>getData()</code> Promise to resolve.</p>
<p>At that point, thanks to the streamable nature of HTML, the server can just send a bit more response HTML in the form of that <code>&#x3C;script></code> tag, which will trigger a next.js (in this case) callback that will update the client-side component with the resolved value.</p>
<h3>See it in action</h3>
<p>I created a simple, live and hosted example of this on my RSC Examples site. You can see this example at https://rsc-examples.edspencer.net/promises/resolved. To view the example directly, skipping the explanation, take a look at https://rsc-examples.edspencer.net/examples/promises/resolved. If you run this curl command you can see the server response streaming in real time:</p>
<pre><code class="language-sh">curl -D - --raw https://rsc-examples.edspencer.net/examples/promises/resolved
</code></pre>
<p>What you'll see there is the server sending the majority of the HTML response, pause for 1 second, then spit out a final <code>&#x3C;script></code> tag that looks like this, along with a <code>&#x3C;div></code> tag that we'll get to in a moment:</p>
<pre><code class="language-html">&#x3C;script>self.__next_f.push([1,"9:[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"},{\"id\":3,\"name\":\"Charlie\"}]\n"])&#x3C;/script>
</code></pre>
<p><code>self.__next_f.push</code> is a next.js function that overrides the <code>push</code> method on the <code>__next_f</code> array, which under the covers fires a bunch of logic to handle whatever the server is sending. I cover that process in a lot more detail in this <a href="/2024/7/1/decoding-react-server-component-payloads">article about how React Server Component Payloads work</a>, but at a high level: the ID that React generated for our <code>dataPromise</code> Promise was ID=9, and so when this <code>&#x3C;script></code> tag is executed, under the covers next.js will figure out that the resolved Promise with ID=9 needs to go back into the <code>dataPromise</code> prop of the <code>Table</code> component.</p>
<p>Now that the promise has resolved on the server, been streamed across to the client and then re-constituted as a Promise again, the client component re-renders, this time with a resolved <code>dataPromise</code> and is therefore able to fully render. We really ended up with 2 Promises - one on the server, the other a reconstituted version of that Promise on the client, but in our code we can treat them as the same thing.</p>
<p>Now let's take a look at that <code>&#x3C;div></code> tag that was also sent by the server (it also has a second <code>&#x3C;script></code> tag tacked on there). I've formatted this slightly to make it more readable as it usually comes down in a single line:</p>
<pre><code class="language-html">&#x3C;div hidden id="S:0">
  &#x3C;table class="max-w-5xl table-auto text-left">
    &#x3C;thead>
      &#x3C;tr>
        &#x3C;th>ID&#x3C;/th>
        &#x3C;th>Name&#x3C;/th>
      &#x3C;/tr>
    &#x3C;/thead>
    &#x3C;tbody>
      &#x3C;tr>
        &#x3C;td>1&#x3C;/td>
        &#x3C;td>Alice&#x3C;/td>
      &#x3C;/tr>
      &#x3C;tr>
        &#x3C;td>2&#x3C;/td>
        &#x3C;td>Bob&#x3C;/td>
      &#x3C;/tr>
      &#x3C;tr>
        &#x3C;td>3&#x3C;/td>
        &#x3C;td>Charlie&#x3C;/td>
      &#x3C;/tr>
    &#x3C;/tbody>
  &#x3C;/table>
&#x3C;/div>
&#x3C;script>
  $RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&#x26;&#x26;8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&#x26;&#x26;"$?"!==d&#x26;&#x26;"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&#x26;&#x26;b._reactRetry()}};
  $RC("B:0","S:0")
&#x3C;/script>
</code></pre>
<p>So at the same time as the server sent the <code>&#x3C;script></code> tag with the resolved Promise, it also sent this <code>&#x3C;div></code> tag with the fully rendered table. This is a neat trick that next.js does to make the page load faster: it sends the server-rendered HTML along with the Promise, so that the client can instantly render the page with the server-rendered HTML, and then hydrate it with the resolved Promise when it arrives.</p>
<p>That second <code>&#x3C;script></code> tag just defines a function that replaces an existing element on the page with id <code>B:0</code> with the element with id <code>S:0</code>. The <code>S:0</code> element is the server-rendered table that just streamed down, and the <code>B:0</code> element is a Suspense-rendered placeholder that allows React to drop this delayed content into the right place in the DOM. When the <code>&#x3C;Table></code> initially attempted to render on the server, but was suspended due to the unresolved Promise, it rendered a placeholder instead of the actual table, with an ID of <code>B:0</code>.</p>
<h3>Limitations and gotchas</h3>
<p>But you can't just send any Promise from the server to the client. The value that the Promise ultimately resolves has to be either a simple native data type like a string, number or float, or a plain JS object/array, or a rendered React component. If the Promise resolves to anything else, you'll get an error on the client side when React tries to render it.</p>
<p>I put together a second example at https://rsc-examples.edspencer.net/promises/various-datatypes that shows the ability to load a variety of different data types. Here's a video of that example in action:</p>
<p></p>
<p>Here we see strings, numbers, floats, plain objects and arrays being sent across the void, as well as a React component, which is a cool thing to be able to do. The React component is a simple one that just renders a string, but it could be anything you like. The full example is at https://rsc-examples.edspencer.net/promises/various-datatypes.</p>
<p>There are ways around this, but that's for another post.</p>
<p>There is a separate example at https://rsc-examples.edspencer.net/promises/rendering-components that focuses on just a React component being rendered without all of the other types so you can see how that works in isolation.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/rsc-promises.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Decoding React Server Component Payloads]]></title>
            <link>https://edspencer.net//2024/7/1/decoding-react-server-component-payloads</link>
            <guid>decoding-react-server-component-payloads</guid>
            <pubDate>Mon, 01 Jul 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you've spent any time playing with <a href="/blog/tag/rsc">React Server Components</a>, you've probably noticed a bunch of stuff like this at the bottom of your web pages:</p>
<pre><code class="language-html">&#x3C;script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])&#x3C;/script>
&#x3C;script>self.__next_f.push([1,"1:HL[\"/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/_next/static/css/app/layout.css?v=1719846361489\",\"style\"]\n0:D{\"name\":\"r0\",\"env\":\"Server\"}\n"])&#x3C;/script>
&#x3C;script>self.__next_f.push([1,"3:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n5:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/client-page.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"ClientPageRoot\"]\n6:I[\"(app-pages-browser)/./app/flight/page.tsx\",[\"app/flight/page\",\"static/chunks/app/flight/page.js\"],\"default\"]\n7:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n8:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\nc:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n4:D{\"name\":\"\",\"env\":\"Server\"}\n9:D{\"name\":\"RootLayout\",\"env\":\"Server\"}\na:D{\"name\":\"NotFound\",\"env\":\"Server\"}\na:[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"childr"])&#x3C;/script>
&#x3C;script>self.__next_f.push([1,"en\":\"This page could not be found.\"}]}]]}]}]]\n9:[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_aaf875\",\"children\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$a\",\"notFoundStyles\":[],\"styles\":null}]}]}]\nb:D{\"name\":\"\",\"env\":\"Server\"}\nd:[]\n0:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/app/layout.css?v=1719846361489\",\"precedence\":\"next_static/css/app/layout.css\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"$L3\",null,{\"buildId\":\"development\",\"assetPrefix\":\"\",\"initialCanonicalUrl\":\"/flight\",\"initialTree\":[\"\",{\"children\":[\"flight\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"flight\",{\"children\":[\"__PAGE__\",{},[[\"$L4\",[\"$\",\"$L5\",null,{\"props\":{\"params\":{},\"searchParams\":{}},\"Component\":\"$6\"}]],null],null]},[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"flight\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\",\"styles\":null}],null]},[\"$9\",null],null],\"couldBeIntercepted\":false,\"initialHead\":[false,\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]]\n"])&#x3C;/script>
&#x3C;script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"React Server Components Payloads\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"By Ed Spencer - edspencer.net\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n4:null\n"])&#x3C;/script>
</code></pre>
<p>You may be wondering what this all means. It's not super well documented, and all pretty bleeding-edge. It's not likely to be something you need to worry about in your day-to-day work, but if you're a curious geek like me, read on.</p>
<p>What you're looking at is a bunch of <code>&#x3C;script></code> tags automatically injected into the end of the page. The content above is a copy-paste from just about the most basic Next JS application imaginable. It consists of 2 components - a layout.tsx and a page.tsx:</p>
<pre><code class="language-layout.tsx">import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "React Server Components Payloads",
  description: "By Ed Spencer - edspencer.net",
};

export default function RootLayout({
  children,
}: Readonly&#x3C;{
  children: React.ReactNode;
}>) {
  return (
    &#x3C;html lang="en">
      &#x3C;body>{children}&#x3C;/body>
    &#x3C;/html>
  );
}
</code></pre>
<pre><code class="language-page.tsx">export default function Home() {
  return (
    &#x3C;div>
      &#x3C;h1>Server Component&#x3C;/h1>
    &#x3C;/div>
  );
}

</code></pre>
<p>In theory, this content could be rendered into the following HTML document - 236 bytes including whitespace:</p>
<pre><code class="language-theoretical-output.html">&#x3C;html lang="en">
  &#x3C;head>
    &#x3C;title>React Server Component Payloads&#x3C;/title>
    &#x3C;meta name="description" content="By Ed Spencer - edspencer.net">
  &#x3C;/head>
  &#x3C;body>
    &#x3C;div>
      &#x3C;h1>Server Component&#x3C;/h1>
    &#x3C;/div>
  &#x3C;/body>
&#x3C;/html>
</code></pre>
<p>Instead, we get about 5kb of stuff, mostly in the form of <code>self.__next_f.push([1, ...])</code> calls. These calls are pushing payloads into an array that Next.js uses to fetch resources and hydrate the page. The payloads are in a custom format that Next.js uses to communicate between the server and the client, called the RSC Payload.</p>
<h3>React Server Component Payloads</h3>
<p>I spent a few hours digging through the React and Next.js source code to figure out what these payloads are and how they work. Instructive in this effort were the following source code files:</p>
<ul>
<li><a href="https://github.com/facebook/react/blob/a1c62b8a7635c0bc51e477ba5437df9be5a9e64f/packages/react-client/src/ReactFlightClient.js#L911">react-client/src/ReactFlightClient.js</a></li>
<li><a href="https://github.com/vercel/next.js/blob/78dc2db916e93ddcffb7418972b40e8d6006fb06/packages/next/src/client/app-index.tsx#L147">next/src/client/app-index.tsx</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js">react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js</a></li>
</ul>
<p>Within <a href="https://github.com/vercel/next.js/blob/78dc2db916e93ddcffb7418972b40e8d6006fb06/packages/next/src/client/app-index.tsx#L147">next/src/client/app-index.tsx</a> we can see that the <code>self.__next_f</code> array is being created, and its <code>push</code> function overridden to call <code>nextServerDataCallback</code> each time <code>__next_f.push(...)</code> is called. Each push to that array is expected to be in the form of a 2-tuple, where the first element is a number between 0 and 3, and the second is some string of data (or undefined if the first element was <code>0</code>).</p>
<p>We can see in both the <a href="https://github.com/vercel/next.js/blob/78dc2db916e93ddcffb7418972b40e8d6006fb06/packages/next/src/client/app-index.tsx#L56">app-index.tsx</a> and the server-side <a href="https://github.com/vercel/next.js/blob/2fceb5b1aa0006941ff749b6b4032f9a68d24318/packages/next/src/server/app-render/use-flight-response.tsx">use-flight-response.tsx</a> the number in the first element means one of 4 things:</p>
<pre><code class="language-use-flight-response.tsx">const INLINE_FLIGHT_PAYLOAD_BOOTSTRAP = 0
const INLINE_FLIGHT_PAYLOAD_DATA = 1
const INLINE_FLIGHT_PAYLOAD_FORM_STATE = 2
const INLINE_FLIGHT_PAYLOAD_BINARY = 3
</code></pre>
<p>Most of the time you're going to see most of the payloads being of type <code>INLINE_FLIGHT_PAYLOAD_DATA</code> (1), which can contain a variety of different types of content.</p>
<h3>Basic format</h3>
<p>The way the payload gets decoded depends on the value of the first element in the array. Let's focus on calls that send <code>INLINE_FLIGHT_PAYLOAD_DATA</code> (1) payloads, as they're the most common and the most interesting. Each payload item contains one or more <code>rows</code>, each of which contains the following parts:</p>
<ul>
<li>ROW_ID: a unique identifier for the payload</li>
<li>ROW_TAG: a string that identifies the type of payload</li>
<li>ROW_DATA: the actual payload data</li>
<li>NEW_LINE: a newline character (<code>\n</code>) indicates the end of the row</li>
</ul>
<p>Let's take a look at that fist injected script:</p>
<pre><code class="language-js">self.__next_f.push([1,"1:HL[\"/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\"
,{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/_next/static/css/app/layout.css?v=1719846361489\"
,\"style\"]\n0:D{\"name\":\"r0\",\"env\":\"Server\"}\n"])
</code></pre>
<p>Here we see that this is a payload of type 1, and the data is a string that contains 3 rows. Each of these rows is separated by a newline character. The first row is a font, the second is a style, and the third is a data row. The data row is a JSON object with a <code>name</code> and <code>env</code> property.</p>
<p>Let's rearrange that to make it a bit easier to understand:</p>
<pre><code class="language-arbitrary-reformulation.json">{
  "rows": [
    {
      "ROW_ID": 1,
      "ROW_TAG": "HL",
      "ROW_DATA": ["_next/static/media/c9a5bc6a7c948fb0-s.p.woff2", "font", {"crossOrigin": "", "type": "font/woff2"}]
    },
    {
      "ROW_ID": 2,
      "ROW_TAG": "HL",
      "ROW_DATA": ["_next/static/css/app/layout.css?v=1719846361489", "style"]
    },
    {
      "ROW_ID": 0,
      "ROW_TAG": "D",
      "ROW_DATA": {"name": "r0", "env": "Server"}
    }
  ]
}
</code></pre>
<p>Ok that's a bit more interesting. So we actually got 3 rows of data in that first <code>&#x3C;script></code> tag. Two of them are of tag type <code>HL</code>, which are "hints" and are ultimately turned into various type of <code>&#x3C;link></code> tags and similar to load CSS, fonts, JS and other resources. The third is a data row, which is used to pass data from the server to the client. The row IDs are not in order, but that doesn't seem to matter very much.</p>
<h3>2kb at a time</h3>
<p>A wrinkle here is that the payloads are limited to 2kb in size. If you try to push a payload larger than that, Next.js will split it into multiple payloads. We see this in the next calls to <code>__next_f.push</code>, which look like this:</p>
<pre><code class="language-js">self.__next_f.push([1,"3:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n5:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/client-page.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"ClientPageRoot\"]\n6:I[\"(app-pages-browser)/./app/flight/page.tsx\",[\"app/flight/page\",\"static/chunks/app/flight/page.js\"],\"default\"]\n7:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n8:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\nc:I[\"(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js\",[\"app-pages-internals\",\"static/chunks/app-pages-internals.js\"],\"\"]\n4:D{\"name\":\"\",\"env\":\"Server\"}\n9:D{\"name\":\"RootLayout\",\"env\":\"Server\"}\na:D{\"name\":\"NotFound\",\"env\":\"Server\"}\na:[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"childr"])

self.__next_f.push([1,"en\":\"This page could not be found.\"}]}]]}]}]]\n9:[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_aaf875\",\"children\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$a\",\"notFoundStyles\":[],\"styles\":null}]}]}]\nb:D{\"name\":\"\",\"env\":\"Server\"}\nd:[]\n0:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/app/layout.css?v=1719846361489\",\"precedence\":\"next_static/css/app/layout.css\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"$L3\",null,{\"buildId\":\"development\",\"assetPrefix\":\"\",\"initialCanonicalUrl\":\"/flight\",\"initialTree\":[\"\",{\"children\":[\"flight\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"flight\",{\"children\":[\"__PAGE__\",{},[[\"$L4\",[\"$\",\"$L5\",null,{\"props\":{\"params\":{},\"searchParams\":{}},\"Component\":\"$6\"}]],null],null]},[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"flight\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\",\"styles\":null}],null]},[\"$9\",null],null],\"couldBeIntercepted\":false,\"initialHead\":[false,\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]]\n"])
</code></pre>
<p>What we're looking at here are 14 rows of data, sent as a single payload but split into two chunks. Whereas in the first example we looked at the payload ended with a <code>\n</code>, here it ends with a <code>]</code>. This is because the payload is split into two parts, and the second part is sent as a separate payload. If you're not into horizontal scrolling, the end of that first line above is:</p>
<pre><code>\":0},\"childr"])
</code></pre>
<p>And the start of the second line is:</p>
<pre><code>self.__next_f.push([1,"en\":\"This page could not be found.\"}]}]]}]}]]\n
</code></pre>
<p>See that the string content of that second one starts with <code>"en"</code>, completing the <code>childr</code> at the end of the first. It's pretty obvious by looking at this that the word <code>children</code> is being split across these two <code>&#x3C;script></code> tags. If we pull up the browser console and look at the length of the first chunk (the one ending in <code>\"childr"]</code>), we see that it's 2048 characters long, which is a pretty notable number.</p>
<p>So if we were to parse the whole payload into our JSON format, we'd end up with something like this:</p>
<pre><code>{
  "rows": [
    {
      "ROW_ID": 3,
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js", ["app-pages-internals", "static/chunks/app-pages-internals.js"], ""]
    },
    {
      "ROW_ID": 5,
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./node_modules/next/dist/client/components/client-page.js", ["app-pages-internals", "static/chunks/app-pages-internals.js"], "ClientPageRoot"]
    },
    {
      "ROW_ID": 6,
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./app/flight/page.tsx", ["app/flight/page", "static/chunks/app/flight/page.js"], "default"]
    },
    {
      "ROW_ID": 7,
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js", ["app-pages-internals", "static/chunks/app-pages-internals.js"], ""]
    },
    {
      "ROW_ID": 8,
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js", ["app-pages-internals", "static/chunks/app-pages-internals.js"], ""]
    },
    {
      "ROW_ID": "c",
      "ROW_TAG": "I",
      "ROW_DATA": ["(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js",["app-pages-internals", "static/chunks/app-pages-internals.js"],""]
    },
    {
      "ROW_ID": 12,
      "ROW_TAG": "D",
      "ROW_DATA": {"name": "", "env": "Server"}
    },
    {
      "ROW_ID": 4,
      "ROW_TAG": "D",
      "ROW_DATA": {"name": "RootLayout", "env": "Server"}
    },
    {
      "ROW_ID": "a",
      "ROW_TAG": "D",
      "ROW_DATA": {"name": "NotFound", "env": "Server"}
    },
    {
      "ROW_ID": "a",
      "ROW_TAG": "D",
      "ROW_DATA": [["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":[["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)"}}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]]
    },
    {
      "ROW_ID": 13,
      "ROW_TAG": "D",
      "ROW_DATA": [["$","html",null,{"lang":"en","children":[["$","body",null,{"className":"__className_aaf875","children":[["$","$L7",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L8",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$a","notFoundStyles":[],"styles":null}]}]}]]}]
    },
    {
      "ROW_ID": 14,
      "ROW_TAG": "D",
      "ROW_DATA": {"name": "", "env": "Server"}
    },
    {
      "ROW_ID": 15,
      "ROW_TAG": "D",
      "ROW_DATA": []
    },
    {
      "ROW_ID": 0,
      "ROW_TAG": "D",
      "ROW_DATA": [[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/app/layout.css?v=1719846361489","precedence":"next_static/css/app/layout.css","crossOrigin":"$undefined"}]],[["$","$L3",null,{"buildId":"development","assetPrefix":"","initialCanonicalUrl":"/flight","initialTree":["",{"children":["flight",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],"initialSeedData":["",{"children":["flight",{"children":["__PAGE__",{},[["$L4",["$","$L5",null,{"props":{"params":{},"searchParams":{}},"Component":"$6"}]],null],null]},["$","$L7",null,{"parallelRouterKey":"children","segmentPath":["children","flight","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L8",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined","styles":null}],null],"$9",null],null],"couldBeIntercepted":false,"initialHead":[false,"$Lb"],"globalErrorComponent":"$c","missingSlots":"$Wd"}]]
    }
  ]
}
</code></pre>
<p>There are a few interesting things going on here. Most of the rows are of type <code>I</code>, which are "imports". These are used to load the various JavaScript components that the page needs. Some of the row IDs are duplicated (<code>"a"</code> appears twice, for example). The rows are out of order, but again this doesn't seem to matter. There are quite a few duplicated strings, but gzip will do a good job with those.</p>
<p>It looks like a default 404 React page is inlined in the response here, which is interesting. Finally, the whole thing is sent in the same HTTP response as the page, so no extra HTTP requests are needed to fetch this data (though of course we will still make a few requests for the resources defined in those <code>I</code> blocks).</p>
<p>Opaque? Yeah kinda, but it is just about scrutable if you have time to wade through the various source files. Let's look at a couple more examples.</p>
<h3>Using Suspense</h3>
<p>Let's swap out the <code>page.tsx</code> file for one that uses Suspense. Note that this is an <code>async</code> function (more on that later):</p>
<pre><code class="language-suspense/page.tsx">import { Suspense } from "react";

async function getData(): Promise&#x3C;string> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("resolved data");
    }, 1000);
  });
}

export default async function SuspensePage() {
  const data = await getData();
  return &#x3C;Suspense fallback={&#x3C;div>Loading...&#x3C;/div>}>{data}&#x3C;/Suspense>;
}
</code></pre>
<p>Our HTML file looks largely the same, but has this additional payload at the end:</p>
<pre><code class="language-html">&#x3C;script>self.__next_f.push([1,"d:\"$Sreact.suspense\"\n5:[\"$\",\"$d\",null,{\"fallback\":[\"$\",\"div\",null,{\"children\":\"Loading...\"}],\"children\":\"resolved data\"}]\n"])&#x3C;/script>
</code></pre>
<p>Or, slightly reformatted:</p>
<pre><code class="language-json">{
  "rows": [
    {
      "ROW_ID": "d",
      "ROW_TAG": undefined,
      "ROW_DATA": "$Sreact.suspense"
    },
    {
      "ROW_ID": 5,
      "ROW_TAG": undefined,
      "ROW_DATA": [["$","$d",null,{"fallback":["$","div",null,{"children":"Loading..."}],"children":"resolved data"}]]
    }
  ]
}
</code></pre>
<p>Looking at <a href="https://github.com/facebook/react/blob/a1c62b8a7635c0bc51e477ba5437df9be5a9e64f/packages/react-client/src/ReactFlightClient.js#L586">the source</a>, we can see that this first row is going to resolve to a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for">Symbol.for</a> lookup for the <code>react.suspense</code> symbol. I didn't trace this through any further but I would bet this is telling Next.js that we're using Suspense and to load the appropriate resources.</p>
<p>Row ID 5 then defines a specification for a React element that will be rendered on the client. It's a Suspense component with a fallback of a div that says "Loading..." and a child of "resolved data". This is the data that was resolved by the <code>getData</code> function in the <code>SuspensePage</code> component. The important bit is the last element there, which is the props that will be passed to the component when it's rendered, via a call to <a href="https://github.com/facebook/react/blob/a1c62b8a7635c0bc51e477ba5437df9be5a9e64f/packages/react-client/src/ReactFlightClient.js#L402">createElement</a>.</p>
<p>It looks like the <code>$d</code> is an internal mapping that will resolve to a React Suspense component. Note that if you watched this HTML response streaming in, you'd see all of the <code>&#x3C;script></code> tags being rendered pretty much immediately, with the exception of this final one that defines the Suspense component, which comes in 1000ms later per our setTimeout. What this means is that we don't actually see the "Loading..." text in the browser, because the Suspense component is only rendered once the data is available. Take a look at <a href="/2024/6/18/understanding-react-server-components-and-suspense">my post on async RSC and Suspense</a> for more on that.</p>
<h3>Using a Promise</h3>
<p>One other thing we can do is return a Promise from our component. This will allow us to see our loading UI. Let's swap out the <code>page.tsx</code> file for one that does this:</p>
<pre><code class="language-promise/page.tsx">import { Suspense } from "react";
import ClientPromise from "./component";

async function getData(): Promise&#x3C;any> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("promise resolved data");
    }, 1000);
  });
}

export default function SuspensePage() {
  return (
    &#x3C;div>
      &#x3C;h1>Server Component&#x3C;/h1>
      &#x3C;Suspense fallback={&#x3C;div>Loading...&#x3C;/div>}>
        &#x3C;ClientPromise dataPromise={getData()} />
      &#x3C;/Suspense>
    &#x3C;/div>
  );
}
</code></pre>
<p>Note that we removed the <code>async</code> from the <code>SuspensePage</code> function, and we're passing the <strong>unresolved</strong> Promise directly into our child <code>ClientPromise</code> component, which looks like this (note that this is a client-side component via <code>use client</code>):</p>
<pre><code class="language-promise/component.tsx">"use client";
import { use } from "react";

export default function ClientPromise({ dataPromise }: { dataPromise: Promise&#x3C;string> }) {
  console.log(dataPromise);
  const data = use(dataPromise);

  return &#x3C;p>{data}&#x3C;/p>;
}
</code></pre>
<p>Neither our server component nor our client component functions are defined as <code>async</code>, but via the magic of the new <code>use</code> hook we still get asynchronous behavior.</p>
<p>Just about the only reason to do this is curiosity, though it may help you debug something if you're having trouble with your Suspense or Server components within Next.js.</p>
<p>That's kinda crazy. We just sent an uncompleted Promise over the wire from the server to the client, and it... worked? What's going on here? Let's look at the payload (snipped for clarity):</p>
<pre><code>&#x3C;script>self.__next_f.push([1,"... snip {\"dataPromise\":\"$@8\"}]}]]}]\n snip ...])&#x3C;/script>
</code></pre>
<p>In this little snippet we see a key called <code>dataPromise</code> (the name of our prop), which has a value of <code>"$@8"</code>. A little later we get this:</p>
<pre><code class="language-html">&#x3C;script>self.__next_f.push([1,"8:\"promise resolved data\"\n"])&#x3C;/script>
</code></pre>
<p>There's our number 8 again. Watching the response streaming in, we can see that this last <code>&#x3C;script></code> tag arrived a second after all the others, and it contains the resolved data from our Promise. Suspense is then used to update the page via this little gem:</p>
<pre><code class="language-html">&#x3C;div hidden id="S:0">
  &#x3C;p>promise resolved data&#x3C;/p>
&#x3C;/div>
&#x3C;script>$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&#x26;&#x26;8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&#x26;&#x26;"$?"!==d&#x26;&#x26;"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&#x26;&#x26;b._reactRetry()}};$RC("B:0","S:0")&#x3C;/script>
</code></pre>
<p>Earlier in the response, Suspense had returned this fallback HTML (note the <code>B:0</code> ID):</p>
<pre><code class="language-html">&#x3C;!--$?-->&#x3C;template id="B:0">&#x3C;/template>&#x3C;div>Loading...&#x3C;/div>&#x3C;!--/$-->
</code></pre>
<p>At the end of the block before, where we have the <code>&#x3C;div hidden id="S:0"></code>, there was a JavaScript function called <code>$RC</code> that basically replaces the <code>B:0</code> element on the page with the contents of the <code>S:0</code> element. This is how the resolved data from the Promise is injected into the page. It's not magic at all, just low-level DOM manipulation.</p>
<p>So in summary, React rendered the fallback, gave it an ID <code>B:0</code>, did some bookkeeping to tell itself that a Promise with ID=8 would be resolving at some point, then when that promise did resolve, it rendered it into a hidden div with ID <code>S:0</code>, as well as into a <code>&#x3C;script></code> tag keyed on the Promise ID so that hydration can work, and then executed some inline JavaScript to replace <code>B:0</code> with <code>S:0</code>.</p>
<h2>Thoughts and Further Reading</h2>
<p>It's totally not necessary to deeply understand how the cogs go round here, but it is fascinating. RSC Payloads are a little arcane and hard to understand, but ultimately they're not magic. I'm not in love with how complex and proprietary this stuff is, but that won't stop me using it. As it matures I'm sure there will be more articles and documentation explaining how and why it works, but for now there's not a lot to go on other than random blog posts like this one.</p>
<p>Some posts and resources that I found really useful in grokking this stuff are:</p>
<ul>
<li><a href="https://www.smashingmagazine.com/2024/05/forensics-react-server-components/">Forensics of React Server Components by Lazar Nokolav</a></li>
<li><a href="https://hrtyy.dev/web/rsc_payload/">RSC Payload &#x26; Serialized Props by hrtyy.dev</a></li>
<li><a href="https://www.jackfranklin.co.uk/blog/react-server-components-deep-dive/">React Server Components: A deep dive by Jack Franklin</a></li>
<li><a href="https://react.dev/reference/rsc/server-components#async-components-with-server-components">Async components with Server Components - react.dev</a></li>
<li><a href="https://github.com/facebook/react/blob/a1c62b8a7635c0bc51e477ba5437df9be5a9e64f/packages/react-client/src/ReactFlightClient.js#L911">ReactFlightClient.js</a></li>
<li><a href="https://github.com/vercel/next.js/blob/78dc2db916e93ddcffb7418972b40e8d6006fb06/packages/next/src/client/app-index.tsx#L147">next/src/client/app-index.tsx</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js">ReactFlightDOMClientBrowser.js</a></li>
</ul>
<p>There's actually quite a bit more to RSC Payloads than I covered here, but this article is long enough already. I'll write a shorter one soon about Promises and the constraints on what you can and cannot send this way via RSC Payloads.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/rsc-payload-head-explode.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[Teams using Next.js and Vercel have an advantage]]></title>
            <link>https://edspencer.net//2024/6/27/teams-using-nextjs-vercel-advantage</link>
            <guid>teams-using-nextjs-vercel-advantage</guid>
            <pubDate>Thu, 27 Jun 2024 16:31:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>During my time at Palo Alto Networks, I spent most of my time working on a product called AutoFocus. It helped cyber security research teams analyze files traversing our firewalls for signs of malware. It was pretty cool, fronted by a large React application, with a bunch of disparate backend services and databases scattered around.</p>
<p>One of the things that was difficult to do was deploy our software. We were on a roughly 3 month release cycle to begin with, which meant several things:</p>
<ul>
<li>Out-of-band bug fix releases were expensive</li>
<li>We didn't get much practice deploying, so when we did, it was a team effort, error prone and took a long time.</li>
<li>Trying to estimate and scope 3 months of work for a team of 10 is a fool's errand</li>
</ul>
<p>Deployment meant getting most of the team into a war room, manually uploading build files to various places, doing a sort of canary deploy, seeing if things seemed ok, then rolling out to the rest of the world. Sometimes we decided to roll out architectural changes to reverse proxies and things at the same time, just for fun.</p>
<p>When I became engineering manager of the UI team for AutoFocus, my top priority was to change all that. We spent 6 months of solid effort to modernize how we built our software, culminating in fully automated CICD to production, staging and unlimited dev environments. It was awesome. It cost us 6 months to get what Vercel gives you out of the box. Don't build it yourself.</p>
<h2>CICD</h2>
<p>It's 2024. If you're not doing CICD you're seriously behind the curve at this point. The top 5 things that prevent engineers from actually building value for your product are, in no particular order:</p>
<ul>
<li>Meetings</li>
<li>Deploying things</li>
<li>Context switching</li>
<li>Low quality codebase</li>
<li>Crappy hardware (<a href="/2024/6/25/hardware-setup-for-software-engineers-2024">see how to do it right</a>)</li>
</ul>
<p>CICD takes care of that second item, by making deployment a non-event. These days, CICD is so easy to achieve with the right technology selections that I'd advise making it one of the first things you do when you start a new project. If you're not automatically deploying code to some environment by the end of the first day, you've probably left it longer than you ought to.</p>
<h3>Review Apps and the joy of unlimited Environments</h3>
<p>Your application needs to run in a bunch of different places. There is the canonical instance your users mainly use, which we usually call <strong>production</strong>. Then there are instances of your application running on your developers' laptops, which we call <strong>development</strong>.</p>
<p>It used to be a common pattern to have a single canonical staging environment, which was a place where one could deploy code before putting it in production. This is generally a poor approach these days. Instead of a single staging instance, which always encounters contention when multiple developers want to deploy code there, multiple PMs and sales folks want to demo from there, QA folks want to test against, and so on, you need to set up your application to run in infinitely many different environments.</p>
<p>In a setup like this, <strong>production</strong>, <strong>demo</strong> and other persistent environments are nothing special compared to any other environment. But in addition to this, it is enormously powerful to have the tip of every branch continually deployed to a unique environment. Some people call these ephemeral environments Review Apps, others Preview Deployments. They are automatically created when a branch is first pushed, and automatically destroyed when the branch is deleted.</p>
<p>Suddenly all of the stakeholders for your application can visit a predictable url like https://my-branch-name.myapp.com and see exactly what the application will look like when that code is deployed to production. Automated integration tests can run against these environments, with the results automatically attached to the PR.</p>
<h3>Prerequisites and what Vercel does for you</h3>
<p>Why did it take us 6 months to roll this stuff ourselves? Well, to pull this off, you need a bunch of things in place:</p>
<ul>
<li>Infrastructure as Code (IaC)</li>
<li>Reliable and trustable automated testing</li>
<li>CICD plumbing code (GitHub or GitLab Actions, Jenkins, etc)</li>
<li>DNS for your Review Apps</li>
<li>SSL cert generation for your environments</li>
</ul>
<p>Some of this will depend on the complexity of your application. CICD Pipeline development is fun, but it's time consuming and usually consists of hundreds of commits and pushes to see if the machine is working as expected. It's a lot of work to get right, and encompasses quite a diverse range of skills. Certainly I had to learn a bunch about Kubernetes, Docker, Terraform, and a bunch of other stuff to get it right, as well as orchestrating DNS entries, SSL certs, ingress controllers, and so on.</p>
<p>Vercel effectively handles the CICD plumbing, DNS hookup and SSL cert generation for you. It does the branch deployment to a Review App environment with an SSL cert and DNS entry out of the box. That's a huge win.</p>
<p>You're still on the hook for automated testing that you actually trust. If you can keep your app architecture simple then you don't actually need to define any IaC code - the key is more that you don't need a human in the loop to create or destroy environments. As far as testing goes, best in class is a set of integration tests that execute against a controlled data set, running on every push to every branch.</p>
<h3>More complex architectures</h3>
<p>There is a point at which this starts to break down. Getting simple apps up and running is very easy, but incorporating a more complex architecture can be difficult to the point of maddening (Heroku suffers spectacularly from this - some of my most inventive cursing ever emanated from trying to deploy to heroku as part of a larger environment deployment). The simpler you can keep your application architecture, the easier it is to build on top of the Vercel platform.</p>
<p>Sometimes that's not realistic though. AutoFocus, for example, relied on a collection of backend services, databases, and other things that are not easily deployable to Vercel. Often there will be petabytes of data in the picture, sometimes attached to written promises that it won't leave your own data center. That massive Elastic Search cluster that holds 10 years of customer data isn't getting migrated into Vercel any time soon.</p>
<p>So how do we deal with this? It will depend on your application, but the general solution is to split your front-end out from the backend stuff. Keep that front end deployable to Vercel and keep all of the productivity benefits that come with it. If your backend is just a relational database and maybe some object storage, keep it all together and deploy to Vercel. If it's significantly more complex than that, split the frontend (and frontend-adjacent) stuff into its own repo so your frontend team can keep moving fast and leverage the productivity benefits of Vercel.</p>
<p>Your backend can't be a ghetto, though. It needs to be IaC, and it almost certainly needs some kind of controlled dataset. Ideally you have a 1:1 mapping between each frontend environment and a pristine backend environment, so that you can run integration tests against the entire system. You can still deploy the UI-centric portion of your application to Vercel using <a href="https://vercel.com/docs/deployments/overview#deploy-hooks">Deploy Hooks</a> - this is useful if you need to trigger a backend deployment as part of your frontend deployment.</p>
<h2>A short dev loop</h2>
<p>UI development benefits enormously from a sub-second developer loop. Within a second of hitting save on a file, I expect to see the following:</p>
<ul>
<li>Updated UI rendered in my browser</li>
<li>Test suite execution and results</li>
</ul>
<p>Messing around with getting CSS right is a miserable process if I have to keep switching between windows, hitting refresh on a browser, or far worse having to build something first. <a href="/2024/6/25/hardware-setup-for-software-engineers-2024">With the proper hardware</a> an engineer can keep their entire development environment in view at all times, and see the effect of their changes immediately. Next.js does this out of the box - <a href="/2024/6/25/hardware-setup-for-software-engineers-2024">you just need the pixels to see it all</a>.</p>
<p>It's hard to overstate how liberating this is for a frontend-centric developer. Coupled with fast CICD, you can iterate at high velocity at all stages of the development process and keep yourself in that high-productivity flow state for much longer.</p>
<p>Beyond what Next.js gives you out of the box, you basically need 2 additional scripts to keep engineers working on a local environment running at high speed:</p>
<ul>
<li>Test suite runner (with file watching). If this takes more than a second on decent hardware, you've probably written the wrong tests.</li>
<li>Controlled Dataset loader. A one-liner to load a realistic dataset into your local environment. Developing against empty datasets leads to crappy user experiences.</li>
</ul>
<h2>Relationship with React</h2>
<p>It has been commented upon that Next.js and React are pretty close bedfellows these days. New features like <a href="/2024/6/18/understanding-react-server-components-and-suspense">React Server Components</a> are only feasible in a handful of frameworks, one of the small number of which is Next.js. Not everyone is thrilled with this state of affairs, grumbling with some justification that you need to be using Next.js to get the most out of React, where React should be a standalone library.</p>
<p>And they're right: to get the most out of React, you should be using Next.js (or another framework like it - though your options are not many). This may have negative implications for the React ecosystem, but if your objective is to build a UI as quickly as possible, you should be using Next.js, and you should probably be deploying to Vercel. A lot of the new tech like <a href="/2024/6/18/understanding-react-server-components-and-suspense">RSCs</a> just aren't going to work otherwise.</p>
<p>The ball is being pushed forward rapidly here, especially in the realm of application performance. RSC, SSR, SSG and ISR are all TLAs that can be game changers for the way your application feels when humans use it. Stuff is going to break and wrong turns are going to be made - the leading edge is a better place to be than the bleeding edge, but the rewards for your team and your users are enormous.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/nextjs-and-vercel.webp" length="0" type="image/webp"/>
        </item>
        <item>
            <title><![CDATA[The best hardware setup for software engineers]]></title>
            <link>https://edspencer.net//2024/6/25/hardware-setup-for-software-engineers-2024</link>
            <guid>hardware-setup-for-software-engineers-2024</guid>
            <pubDate>Tue, 25 Jun 2024 16:31:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>When I'm writing software I usually have the following windows open, all at the same time:</p>
<ul>
<li>2 column layout VS Code (window = 2560 x 2160)</li>
<li>A fullscreen-equivalent browser with usable console to see what I'm working on (window = 1920 x 2160)</li>
<li>A large terminal window (window = 2560 x 1440)</li>
<li>Chat GPT (window = 2560 x 1440)</li>
<li>A full-screen browser with all the stuff I'm researching (window = 2560 x 2880)</li>
</ul>
<p>I find tabbing between windows to be a great destroyer of productivity, so I've spent a good deal of time and money over the last few years iterating on a hardware setup that lets me see everything at once. Today, it looks like this:</p>
<p>I went through a number of iterations when it comes to monitors. For a long time I used dual 32" 4K IPS screens, but even that wasn't quite enough pixels. It's hard to physically fit more than 2 32" screens on a desk - they're too wide already, and it would not be ergonomic to mount them above each other.</p>
<p>About a year ago I discovered these <a href="https://amzn.to/4c8mbwO">16:18 ratio screens</a> and bought 2 of them as side screens around the central 32" 4k screen. They're 2560x2880, providing ~7m pixels each, compared to ~8m pixels on a 4k screen. Another way to look at it is two 1440p screens stacked on top of each other.</p>
<h2>Aim for 20 million pixels</h2>
<p>When I was a little younger, the most coveted monitor was the Apple Cinema Display. At 27" it was a good size, and at 1440p (2560px x 1440px) it had a lot of pixels for the time. They had excellent picture quality and went for a thousand dollars each.</p>
<p>I had 2 of these side by side, which filled my desk. Between them they gave me about 7 million pixels, with little prospect for adding more due to the physical constraints of a reasonable desk.</p>
<p>Each of those 16:18 side screens provide the same pixel count as those 2 Apple screens combined, effectively stacked one on top of the other. Splitting the 4k display into 4x 1080p windows yields a potential layout like this:</p>
<p>Each blue box represents a genuinely full-screen-equivalent window. The gray 1920x1080 windows are a little too small for my liking when it comes to software engineering; here's how I actually lay it out:</p>
<p>The yellow box is a full-height browser window. It is 1/3rd the width of the 4k screen, which gives it a very pleasant 1280px width (this happens to line up with a tailwind breakpoint). At 2160px height, it's tall enough that I can have a 1000px tall dev tools console open at the same time as the content I'm working on, without compromising on either.</p>
<p>The green box is a double-height browser window, and excellent for reading developer documentation, GitHub issues and the like. Scanning through lots of text is far more efficient when you have 2880px of vertical height.</p>
<p>It's really flexible to have the side screens work like this, and throughout the day I will often switch between having a single double-height window (e.g. green box) and two 1440p windows stacked on top of each other (e.g. 2x blue boxes). The mac app <a href="https://rectangleapp.com/">Rectangle</a> makes that trivial with a couple of keyboard shortcuts.</p>
<h2>How it feels to use</h2>
<p>Here's what it actually looks like when you have a single window on that 16:18 screen. Browsing documentation this way is a dream because you can see so much content at once, without compromising on hiding other windows behind it.</p>
<p>Another huge benefit that the 3 monitor setup has over the 2 monitor is that you can now have a screen that is facing you directly, instead of 2 screens that are both on an angle. I probably spend 80% of the time looking at the content on the center screen, and while doing so I'm not having to turn my head to look at one screen or the other.</p>
<p>For the center screen I went with a 32" 4K IPS display. It cost the best part of a thousand dollars, but I spend thousands of hours a year using it and it's high quality enough that it doesn't hurt my eyes. I use <a href="https://rectangleapp.com/">Rectangle</a> to manage the windows on the screen - the right 2/3s is for VS Code, the left 1/3 is dedicated to a browser window for whatever app I'm working on:</p>
<p>Here, without switching windows, I have 2 full height, full width code editors, so I can see a hundred lines of one file while working on another, and a full-height browser window, which is 1280px wide and tall enough to have a genuinely usable dev tools console. The monitor is physically large enough that all of the text is comfortably legible.</p>
<p>The right hand screen is another 16:18 monitor, and I typically keep a half-height Chat GPT window open, and a half-height terminal window. Bear in mind that half-height here is still 2160px x 1440px, which is a full screen application on any monitor with less than 4k resolution, and we get 2 of them per side monitor:</p>
<h3>Laptops and other hardware</h3>
<p>Obviously, your computer needs to be powerful enough to drive these 20 million pixels without skipping a beat. Micro-context switches that happen when you have to wait a few seconds when you do something really add up and suck away your productivity.</p>
<p>I'm using a maxed-out MacBook Pro with an M2 Max CPU and 64Gb of RAM. It basically never gets in my way regardless of what I throw at it. They've released the M3 since then, and these things are only going to get more capable. I expect this machine will provide me with an excellent developer experience for several years before I replace it. The laptop form factor is useful; although I lose 70% of my pixels when I code on the couch, it is nice to be able to move around the house and work in different places.</p>
<p>I use a <a href="https://amzn.to/45zMvO5">gray bluetooth Apple keyboard</a> and a <a href="https://amzn.to/3zhzY5P">Logitech MX Master 3</a> mouse. The keyboard is laughably expensive, but it ties the room together with its matching color scheme. The mouse is excellent, and can be paired with 3 different devices, so I can switch between my laptop and my PC desktop machine pretty easily.</p>
<h3>Desk and chair</h3>
<p>If you're going to be at a desk many hours per day, it's a good idea to get a standing desk so that you're not sitting all day long. Ten years ago I bought a <a href="https://www.xdesk.com/terra">xdesk terra</a>, which cost a couple of thousand and was pretty good, but then I moved house and had a little more space so upgraded to a 80" wide <a href="https://www.upliftdesk.com/uplift-v2-solid-wood-standing-desk/">Uplift Desk</a>. The Uplift also cost about $2k, but it's big and very stable and I expect it will also last me at least a decade.</p>
<p>I have a <a href="https://www.hermanmiller.com/products/seating/office-chairs/aeron-chairs/">Herman Miller Aeron</a> chair, which is also expensive but very comfortable. I've had it for 10 years and it's still in great condition. I've had a couple of cheaper chairs in the past, and they've been ok but not great. You don't need to spend a grand on a chair, but it's important to get one that you forget you're sitting on, otherwise it'll just be a distraction.</p>
<h3>Creature Comforts</h3>
<p>If your office is comfortable you will want to spend more time in it. Little things like adding <a href="https://amzn.to/3VHBTrA">Philips Hue light bars</a> to the backs of the monitors and spending a little time setting up the colors and brightness can make a big difference to the ambience of the room. Airpod Pros are great for meetings, especially with the noise cancellation turned on.</p>
<h3>Putting it all together</h3>
<p>If I was starting from scratch, I'd buy the following, which happens to be my current setup:</p>
<p>With sales tax, cables and other paraphernalia, you're looking at about $10k for a top-of-the-line setup. This is a lot of money, but most of this will last several years, and the economics are heavily in your favor, as we'll discover in this final section.</p>
<h3>How companies should think about this</h3>
<p>A lot of companies will give their engineers a setup like this:</p>
<p>Here's how that looks in terms of pixels. I will generously assume that the monitors are 2560 x 1440, and am showing that right screen oriented in landscape mode:</p>
<p>Those two blue boxes are the same size as the ones in the diagrams earlier, except instead of this being what you get per side screen, not counting the main screen, this is the whole ball game. Why would you do this to your engineers? That's only giving them a third of the pixels they could use to maximize their productivity.</p>
<p>A good engineer costs a lot of money - hundreds of thousands of dollars per year. Even more for senior folks. Given that the company employing them needs to make a profit, the average engineer probably needs to generate in the order of $1m per year in value to justify the expense to the company.</p>
<p>Therefore, for the typical engineer, <strong>an increase in productivity of 1% is worth about $10,000 per year</strong>. In other words, you could buy each of your engineers all of that $10,000 of brand new equipment on January 1st, have them smash it all to pieces on December 31st and do the same thing again the next year, and you'd still be ahead so long as they were 1% more productive.</p>
<p>I would conservatively estimate that a setup like this is at least 10% more productive than what usually gets served up. Given that you're going to spend a minimum of $5k even to kit out your engineer poorly, the marginal cost here is actually just a few thousand bucks, and most of it comes down to screen real estate.</p>
<p>Scaling engineering organizations and teams get less efficient as they get bigger, so equip your engineers with the best tools that exist and you'll get more done with fewer people.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/desk-good.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Loading Fast and Slow: async React Server Components and Suspense]]></title>
            <link>https://edspencer.net//2024/6/18/understanding-react-server-components-and-suspense</link>
            <guid>understanding-react-server-components-and-suspense</guid>
            <pubDate>Tue, 18 Jun 2024 16:31:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>When the web was young, HTML pages were served to clients running web browser software that would turn the HTML text response into rendered pixels on the screen. At first these were static HTML files, but then things like PHP and others came along to allow the server to customize the HTML sent to each client.</p>
<p>CSS came along to change the appearance of what got rendered. JavaScript came along to make the page interactive. Suddenly the page was no longer the atomic unit of the web experience: pages could modify themselves right there inside the browser, without the server being in the loop at all.</p>
<p>This was good because the network is slow and less than 100% reliable. It heralded a new golden age for the web. Progressively, less and less of the HTML content was sent to clients as pre-rendered HTML, and more and more was sent as JSON data that the client would render into HTML using JavaScript.</p>
<p>This all required a lot more work to be done on the client, though, which meant the client had to download a lot more JavaScript. Before long we were shipping <em>MEGABYTES</em> of JavaScript down to the web browser, and we lost the speediness we had gained by not reloading the whole page all the time. Page transitions were fast, but the initial load was slow. Megabytes of code shipped to the browser can multiply into hundreds of megabytes of device memory consumed, and not every device is your state of the art Macbook Pro.</p>
<p>Single Page Applications ultimately do the same thing as that old PHP application did - render a bunch of HTML and pass it to the browser to render. The actual rendered output is often a few kilobytes of plain text HTML, but we downloaded, parsed and executed megabytes of JavaScript to generate those few kilobytes of HTML. What if there was a way we could keep the interactivity of a SPA, but only send the HTML that needs to be rendered to the client?</p>
<h2>Enter React Server Components</h2>
<p>React Server Components are one of the biggest developments in React for years, with the potential to solve many of these problems. RSCs allow us to split our page rendering into two buckets - Components rendered on the client (traditional React style) and components rendered on the server (traditional web style).</p>
<p>Let's say we're building an application to help us manage devices, so we want some CRUD. Probably we're going to have a Devices index page where we can look at the list of Devices, and then either click on one to see the details, or click a button to create a new one. We might also want to edit or delete devices.</p>
<p>In the traditional React client-side mindset, we would build ourselves a page that will be rendered in the browser - it will need to fetch the Devices data from our backend, wait until the response comes back, handle any errors, and then render the list of devices. We might use a library like SWR to handle the fetching and caching of the data, and we might use a library like React Query to handle the mutation of the data. You've probably written this component a thousand times.</p>
<p>Maybe we'd end up with something that looks like this:</p>
<p>You've seen the code to do this on the client side a thousand times before, with all its useState, useEffect, fetch, try/catch and other boilerplate. It's easy to create bugs in this code, to forget to handle edge cases, and to end up with a page that doesn't work as expected. What if we could write it like this instead?</p>
<pre><code class="language-app/devices/page.tsx">import { getDevices } from "@/models/device";

import { Heading } from "@/components/common/heading";
import { Button } from "@/components/common/button";
import DevicesTable from "@/components/device/table";

export default async function DevicesPage() {
  const devices = await getDevices();

  return (
    &#x3C;div>
      &#x3C;div className="flex w-full flex-wrap items-end justify-between gap-4 pb-6">
        &#x3C;Heading>Devices&#x3C;/Heading>
        &#x3C;div className="flex gap-4">
          &#x3C;Button href="/devices/create">Add Device&#x3C;/Button>
        &#x3C;/div>
      &#x3C;/div>
      &#x3C;DevicesTable devices={devices} />
    &#x3C;/div>
  );
}
</code></pre>
<p>This is a React Server Component. In this brave new world, you can tell it's a server component because the file doesn't start with 'use client'. RSCs are still pretty new and only supported in frameworks like NextJS that have a server-side rendering capability. By default, all components in NextJS are server components <a href="https://nextjs.org/docs/app/building-your-application/rendering/client-components">unless your file starts with 'use client'</a>.</p>
<p>The main thing this component is doing is fetching device data via the <code>getDevices</code> function, which is all running on the server side and probably reading from a database. By doing this on the server, we avoid a) an extra HTTP round-trip to fetch the data separately from the React component, and b) all of the client-side logic required to make that work. Our code is clean and simple, with the magic of async/await making it read as though its synchronous, which is easier on human brains.</p>
<p>Let's have a quick look at the layout.tsx file that this component is rendering into:</p>
<pre><code class="language-layout.tsx">export default function RootLayout({
  children,
}: Readonly&#x3C;{
  children: React.ReactNode;
}>) {
  return (
    &#x3C;html lang="en">
      &#x3C;body>
        {children}
      &#x3C;/body>
    &#x3C;/html>
  );
}
</code></pre>
<p>Ok that's about as basic as it gets. The RootLayout component is also a React Server Component - it gets rendered on the server and the resulting HTML is sent to the client. When we visit the /devices URL, the server will render the <code>app/devices/page.tsx</code> component and shove it where we put <code>{children}</code> in the layout.tsx file.</p>
<p>You can see a live, real life Next JS application demonstrating how these different approaches feel at https://rsc-suspense-patterns.edspencer.net/. The code is super simple and available at https://github.com/edspencer/rsc-suspense-patterns.</p>
<p>But there's a wrinkle here - our <code>DevicesPage</code> component is defined as an <code>async</code> function. That's because, in this case, we need to make some asynchronous calls to fetch the data we need to render the page. So of course it's got to be async, but how does that mesh with our synchronous rendering of the layout and returning of the response to the client?.</p>
<p>Well, by default, it means that the server will have to wait for the async <code>DevicesPage</code> function to finish before it can render the page and send it to the client. If our database lookup is slow, this means the user is sat looking at a completely blank screen for a while. Not a great user experience.</p>
<p>To convince you of this, I created a skeleton Next JS application that is currently running at https://rsc-suspense-patterns.edspencer.net/. It has 5 pages, all of which are React Server Components, and all of which have different treatments of the async data fetching. The code for this application is available at https://github.com/edspencer/rsc-suspense-patterns.</p>
<h3>Vanilla async Server Component rendering</h3>
<p>The first page in my little skeleton app is at https://rsc-suspense-patterns.edspencer.net/slow/no-suspense - the best thing to do is open that in a new window at watch it load. You'll see nothing happen for 3 seconds, then suddenly the whole page appears at once. This is because the <a href="https://github.com/edspencer/rsc-suspense-patterns/blob/main/app/slow/no-suspense/page.tsx">page.tsx for that URL</a> is exactly what I show you in the code block above - an async function that fetches some data and then returns it. The call to <code>getDevices</code> there just waits 3 seconds before returning a static array of fake data.</p>
<p>This page feels broken, right? Nothing happens for 3 seconds, which is more than enough time to make a user think the page is broken and leave. With React Suspense, though, we can do better than this, starting with the next page in my little app.</p>
<h3>Page-level Suspense boundaries with loading.tsx</h3>
<p>Next.JS provides a nice little convention for providing page-level Suspense behavior, including React Server Component pages. Suspense, if you're not familiar with it, is a way for your React application to render everything that it can, show that to the user, and when the rest of the components on the page are ready to be rendered, stream them into the browser.</p>
<p>With Next.JS, we can just create a <code>loading.tsx</code> file in the same directory as our page.tsx file, and it will be used as a fallback while the page is loading. This is a great way to show a loading spinner or other loading indicator to the user while the page is loading. Here's how simple that can be:</p>
<pre><code class="language-app/slow/suspense/loading.tsx">export default function Loading() {
  return (
    &#x3C;div className="flex justify-center items-center h-64">
      &#x3C;div className="animate-spin rounded-full h-16 w-16 border-b-2 border-gray-900">&#x3C;/div>
    &#x3C;/div>
  );
}
</code></pre>
<p>Just by defining this file, Next.js did a little work under the covers, resulting in the following behavior:</p>
<ul>
<li>When the page first loads, the <code>page.tsx</code> component rendering is initiated, but doesn't render immediately</li>
<li>While that async function is fetching data/doing whatever else before rendering, the <code>loading.tsx</code> component is rendered instead</li>
<li>When the async function finishes, the <code>page.tsx</code> component is rendered and replaces the <code>loading.tsx</code> component</li>
</ul>
<p>You can see this in action at https://rsc-suspense-patterns.edspencer.net/slow/suspense. Again, to really see what is going on there, open the link in a brand new browser tab/window. This time, we get the page header menu rendering immediately - it is part of <code>layout.tsx</code>, and for 3 seconds we see our <code>loading.tsx</code> render - a spinner in this case. After 3 seconds, the <code>page.tsx</code> component renders and replaces the spinner:</p>
<h3>Component-level suspense boundaries</h3>
<p>Page-level Suspense boundaries are an improvement to our vanilla version because at least we're rendering some of our application immediately, and showing the user that something is happening via a loading spinner. It's also super-easy to just drop a <code>loading.tsx</code> file into a component directory and have it work.</p>
<p>But we can do better than that. We can use Suspense boundaries at the component level to show the user that something is happening at a more granular level. Here's the <a href="https://github.com/edspencer/rsc-suspense-patterns/blob/main/app/slow/component-suspense/page.tsx">actual source code</a> that powers the third and final slow loading RSC page in my demo - which you can see live at https://rsc-suspense-patterns.edspencer.net/slow/component-suspense:</p>
<pre><code class="language-app/slow/component-suspense/page.tsx">import { getDevices } from "@/models/device";

import Heading from "@/components/common/heading";
import DevicesTable from "@/components/device/table";
import AddDeviceButton from "@/components/device/button";

import Loading from "@/components/common/loading";
import { Suspense } from "react";

export default function DevicesPage() {
  return (
    &#x3C;>
      &#x3C;div className="flex w-full flex-wrap items-end justify-between gap-4 pb-6">
        &#x3C;Heading>Devices (3000ms database call, Component-level Suspense)&#x3C;/Heading>
        &#x3C;div className="flex gap-4">
          &#x3C;AddDeviceButton />
        &#x3C;/div>
      &#x3C;/div>
      &#x3C;Suspense fallback={&#x3C;Loading />}>
        &#x3C;LoadedDevicesTable />
      &#x3C;/Suspense>
      &#x3C;p>
        On this screen, we get all of the page contents rendered instantly (including this paragraph),
        but see a loading spinner while the table is loaded, rendered, and streamed back to the client.
      &#x3C;/p>
    &#x3C;/>
  );
}

async function LoadedDevicesTable() {
  const devices = await getDevices();

  return &#x3C;DevicesTable devices={devices} />;
}
</code></pre>
<p>We've done three things here:</p>
<ol>
<li>We split the loading and rendering of the <code>&#x3C;DevicesTable></code> into a separate (async) component called <code>&#x3C;LoadedDevicesTable></code></li>
<li>We made our <code>DevicesPage</code> component synchronous, so it renders immediately</li>
<li>We wrapped our new <code>&#x3C;LoadedDevicesTable></code> component in a <code>&#x3C;Suspense></code> component, with a <code>fallback</code> prop that renders our loading spinner</li>
</ol>
<p>If you open up the <a href="https://rsc-suspense-patterns.edspencer.net/slow/component-suspense">live demo page</a>, you'll see that the entire page renders instantly, including the header and footer, and the paragraph explaining what's going on. The only thing that doesn't render immediately is the data table, which shows a loading spinner until the data is fetched and the table is rendered.</p>
<p>This is a much better user experience than the vanilla version, and even the page-level Suspense version. It's a great way to show the user that something is happening, and that the page isn't broken, while still rendering as much of the page as possible immediately. Adding a <code>&#x3C;Suspense></code> wrapper is every bit as easy as adding a <code>loading.tsx</code> file, and will often produce a better user experience.</p>
<p>Now your application is ~90% rendering on the server side, using React Server Components, and only the interactive parts are rendered on the client side. This is a great way to get the best of both worlds - the speed and reliability of server-side rendering, and the interactivity of client-side rendering.</p>
<h2>Implications for React Server Components</h2>
<p>Generally speaking, if a page requires several database/RPC calls to load its data, it will usually be significantly faster to render that page on the server side than on the client side. This is because the server usually has a fast, low-latency connection to the database, and can render the page in a single pass.</p>
<p>But this is not a panacea - databases that started out fast often become slow over time. UX patterns (like not using Suspense) that made total sense with a 10ms data fetch can become a problem when that fetch takes 3000ms or more. If you start to one or more of those slow data fetches on a page, you're not going to be giving your users a great experience if you use async React Server Components at the page level.</p>
<h3>Consider making page-level RSCs synchronous</h3>
<p>The approach in the code block below (<a href="https://github.com/edspencer/rsc-suspense-patterns/blob/main/app/slow/component-suspense/page.tsx">which is the same approach as above</a>) is one way to get around that, where we split the async code out of the Page component. By confining ourselves to rendering only synchronous components at the page level, we can render the page immediately and then stream in the async components as they're ready. This is a great way to give the user a sense of progress and keep them engaged with the page.</p>
<pre><code class="language-app/my-lovely-horse/page.tsx">import Loading from "@/components/common/loading";
import { Suspense } from "react";

//synchronous - fast!
export default function FastRSCPage() {
  return (
    &#x3C;>
      &#x3C;h2>My lovely page&#x3C;/h2>
      &#x3C;Suspense fallback={&#x3C;Loading />}>
        &#x3C;SlowLoadingComponent />
      &#x3C;/Suspense>
    &#x3C;/>
  );
}

//async - can be slow but doesn't matter as it's not at the page level
async function SlowLoadingComponent() {
  const devices = await getDevices();

  return &#x3C;DevicesTable devices={devices} />;
}
</code></pre>
<p>In this approach, our <code>&#x3C;FastRSCPage></code> and <code>&#x3C;SlowLoadingComponent></code> components are both still React Server Components. They even happen to be in the same file, though they don't have to be. It's just that splitting the async code out of our top-level component (the "page") means that we can render as much of the UI as possible, essentially instantly.</p>
<h3>Page Interactivity waits for Suspense (sometimes)</h3>
<p>Our little page has an <code>Add Device</code> button, which is the only <code>'use client'</code> component in the entire app. <a href="https://github.com/edspencer/rsc-suspense-patterns/blob/main/components/device/button.tsx">All it does in this demo is fire an alert</a>, which ought to convince you it is a component running in the browser.</p>
<p>But if you open up https://rsc-suspense-patterns.edspencer.net/slow/component-suspense and click the <code>Add Device</code> button while the spinner is still spinning, nothing happens. Click it again after the spinner goes away, and you'll see the alert. This might be a little unexpected - the button is in the synchoronous part of the page, not within the Suspense boundary, so why doesn't it work?</p>
<p>I actually don't know. React 18 came along with <a href="https://github.com/reactwg/react-18/discussions/37">an excellent post explaining how Suspense is supposed to work</a>, including Selective Hydration. Hydration is when you render your page HTML on the server side, the client downloads it, then React spins up in the client and attaches itself to all that lovely HTML the server sent down. Until Hydration is complete, your React app may be mostly rendered, but it is not interactive.</p>
<p>Selective Hydration is supposed to enable React to automatically hydrate the parts of your application that are fully rendered, running hydration again for any components inside <code>&#x3C;Suspense></code> boundaries that were not ready the first time hydration occurred.</p>
<p>This should mean that the <code>Add Device</code> button is interactive as soon as the page is hydrated, even if the data table is still loading. As you'll note, it doesn't seem to actually do that, so watch out for behavior like this in your own apps. All of this stuff is pretty new, so it's possible that there are still some bugs to be ironed out. If I figure that out I'll let you know.</p>
<h2>Conclusions and further reading</h2>
<p>React Server Components are a powerful new feature in React that can be a game-changer for the UX of your applications when implemented correctly. They're also a Big Rewrite trap that could seem annoying if you have thousands of hours invested in a React app that works the Old Way. But if you're starting a new project, or have a project that's not working as well as you'd like, they're definitely worth a look.</p>
<p>I read some excellent posts by some fine folks while embarking on my own journey of understanding around this topic - here are three articles on RSC that you should consider reading:</p>
<ul>
<li><a href="https://www.joshwcomeau.com/react/server-components/">Making Sense of React Server Components</a> - an excellent intro to the concept by Josh Comeau</li>
<li><a href="https://www.smashingmagazine.com/2024/05/forensics-react-server-components/">The Forensics Of React Server Components</a> - goes into the weeds on how this stuff works under the hood</li>
<li><a href="https://github.com/reactwg/react-18/discussions/37">New Suspense SSR Architecture in React 18</a> - the source of truth, from the horse's mouth</li>
</ul>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/rsc-examples-component-suspense.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Using Server Actions with Next JS]]></title>
            <link>https://edspencer.net//2024/6/4/using-server-actions-with-nextjs</link>
            <guid>using-server-actions-with-nextjs</guid>
            <pubDate>Tue, 04 Jun 2024 16:31:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>React and Next.js introduced <a href="https://react.dev/reference/rsc/server-actions">Server Actions</a> a while back, as a new/old way to call server-side code from the client. In this post, I'll explain what Server Actions are, how they work, and how you can use them in your Next.js applications. We'll look at why they are and are not APIs, why they can make your front end code cleaner, and why they can make your backend code messier.</p>
<h2>Everything old is new again</h2>
<p>In the beginning, there were <code>&#x3C;form></code>s. They had an <code>action</code>, and a <code>method</code>, and when you clicked the submit button, the browser would send a request to the server. The server would then process the request and send back a response, which could be a redirect. The <code>action</code> was the URL of the server endpoint, and the <code>method</code> was usually either <code>GET</code> or <code>POST</code>.</p>
<pre><code class="language-html">&#x3C;form action="/submit" method="POST">
  &#x3C;input type="text" name="name" />
  &#x3C;button type="submit">Submit&#x3C;/button>
&#x3C;/form>
</code></pre>
<p>Then came AJAX, and suddenly we could send requests to the server without reloading the page. This was a game-changer, and it opened up a whole new world of possibilities for building web applications. But it also introduced a lot of complexity, as developers had to manage things like network requests, error handling, and loading states. We ended up building React components like this:</p>
<pre><code class="language-TheOldWay.jsx">//this is just so 2019
export default function CreateDevice() {
  const [name, setName] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    try {
      await fetch('/api/devices', {
        method: 'POST',
        body: JSON.stringify({ name }),
        headers: {
          'Content-Type': 'application/json',
        },
      });
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };

  return (
    &#x3C;form onSubmit={handleSubmit}>
      &#x3C;input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      &#x3C;button type="submit" disabled={loading}>Submit&#x3C;/button>
      {error &#x26;&#x26; &#x3C;p>{error.message}&#x3C;/p>}
    &#x3C;/form>
  );
}
</code></pre>
<p>This code is fine, but it's a lot of boilerplate for something as simple as submitting a form. It's also not very readable, as the logic for handling the form submission is mixed in with the UI code. Wouldn't it be nice if we could go back to the good old days of <code>&#x3C;form></code>s, but without the page reload?</p>
<h2>Enter Server Actions</h2>
<p>Now, with Server Actions, React is bringing back the simplicity of the old days, while still taking advantage of the power of modern web technologies. Server Actions allow you to call server-side code from the client, just like you would with a traditional form submission, but without the page reload. It wants you to think that this is all happening without an API on the backend, but this isn't true. It's not magic after all.</p>
<p>Here's how we can write the same form using Server Actions:</p>
<pre><code class="language-app/components/AddDeviceForm.tsx">'use client';
import { useFormState } from 'react-dom';
import { createDeviceAction } from '@/app/actions/devices';

export function AddDeviceForm() {
  const [state, formAction] = useFormState(createDeviceAction, {});

  return (
    &#x3C;form action={formAction} className="create-device">
      &#x3C;fieldset>
        &#x3C;label htmlFor="name">Name:&#x3C;/label>
        &#x3C;input type="text" name="name" id="name" placeholder="type something" />
        &#x3C;button type="submit">Submit&#x3C;/button>
      &#x3C;/fieldset>
      {state.status === 'error' &#x26;&#x26; &#x3C;p className="text-red-500">{state.message}&#x3C;/p>}
      {state.status === 'success' &#x26;&#x26; &#x3C;p className="text-green-500">{state.message}&#x3C;/p>}
    &#x3C;/form>
  );
}
</code></pre>
<p>Here's that same <code>AddDeviceForm</code> Component running live in this page. It's a real React component, so try submitting it with and without text in the input field. In both cases it's hitting our <code>createDeviceAction</code> function, which is just a simple function that returns a success or error message based on the input:</p>
<p>The form components on this page are real React components, running in the browser. You can interact with them just like you would with any other web page. The code for these components is copy/pasted from the actual code running behind the scenes, so you can see exactly how it works.</p>
<p>The only thing I've done is add some styling to make it look nice. The actual code is just the component and the action function, nothing else.</p>
<p>One nice thing about this is that the Enter key works on your keyboard without any extra code. This is because the form is a real form, and the submit button is a real submit button. The <code>formAction</code> hook is doing the work of intercepting the form submission and calling the server action instead of the default form submission. It feels more like the old school web.</p>
<p>And here's the actual server action that is being called, in a file called <code>app/actions/devices.ts</code>:</p>
<pre><code class="language-app/actions/devices.ts">'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
  const name = formData.get('name');

  if (name) {
    const device = {
      name,
      id: Math.round(Math.random() * 10000),
    };

    return {
      status: 'success',
      message: `Device '${name}' created with ID: ${device.id}`,
      device,
    };
  } else {
    return {
      status: 'error',
      message: 'Name is required',
    };
  }
}
</code></pre>
<p>The code here is simulating a database mutation and doing some basic validation. This all ought to look pretty familiar. Again, this is the actual copy/pasted code actually running behind the scenes.</p>
<p>This is a very simple example, but in a real-world application you would likely want to add some kind of authentication to your server actions. These server action endpoints are as wide open as any other, so you need to reason about them in the exact same way when it comes to authentication and authorization.</p>
<h2>How does this work?</h2>
<p>We didn't set up any API routes, we didn't write any network request code, and we didn't have to handle any loading states or error handling. There is no code I am not showing you, stitching things together. We just wrote a simple form, and the Server Actions library took care of the rest. It's like magic!</p>
<p>But it's not magic. It's HTTP. If you open up your browser's developer tools and submit the form, you'll see a network request being made to the server, just like with a traditional form submission. The only difference is that the request is being intercepted by the Server Actions library and handled by the <code>createDeviceAction</code> function instead of the default form submission handler. This results in a POST request being sent to the current URL, with the form data and a bunch of other stuff being sent along with it.</p>
<p>Here's what the response looked like:</p>
<p>Next.js has basically created an API endpoint for us, and then provided its own wrapper calls and data structures on both the request and response cycles, leaving us to focus solely on our UI and business logic.</p>
<h2>Visual feedback for slower requests</h2>
<p>In many cases, the backend may take a few seconds to process the user's request. It's always a good idea to provide some visual feedback to the user while they are waiting. There's another lovely new React hook called <code>useFormStatus</code> that we can use to show a loading spinner while the request is pending. Here's a slightly modified version of the form that shows gives the user some feedback while the request is being processed:</p>
<pre><code class="language-app/components/AddDeviceFormSlow.tsx">'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createDeviceActionSlow } from '@/app/actions/devices';

export function AddDeviceFormSlow() {
  const [state, formAction] = useFormState(createDeviceActionSlow, {});

  return (
    &#x3C;form action={formAction} className="create-device">
      &#x3C;fieldset>
        &#x3C;label htmlFor="name">Name:&#x3C;/label>
        &#x3C;input type="text" name="name" id="name" placeholder="type something" />
        &#x3C;SubmitButton />
      &#x3C;/fieldset>
      {state.status === 'error' &#x26;&#x26; &#x3C;p className="text-red-500">{state.message}&#x3C;/p>}
      {state.status === 'success' &#x26;&#x26; &#x3C;p className="text-green-500">{state.message}&#x3C;/p>}
    &#x3C;/form>
  );
}

//this has to be a separate component because we can't use the useFormStatus hook in the 
//same component that has the &#x3C;form>. Sadface.
function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    &#x3C;button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    &#x3C;/button>
  );
}
</code></pre>
<p>This is almost identical to the first example, but I've split the submit button into a separate component and used the <code>useFormStatus</code> hook to show a loading spinner when the request is pending. It's also now pointing at the <code>createDeviceActionSlow</code> function, which is identical to the <code>createDeviceAction</code> function except it has a 3 second delay before returning the response.</p>
<p>In this brave new world, we are in a position to load data either on the client or the server, process actions either on the client or the server, and render components either on the client or the server. It's a lot to keep track of, but it's also a lot of power.</p>
<p>Here's the live component - give it a whirl:</p>
<p>That's pretty cool. The <code>useFormStatus</code> hook is doing all the work of tracking the request status and updating the UI accordingly. It's a small thing, but it makes both the user experience and the developer experience a lot better.</p>
<p>Just to confuse things a little, <a href="https://react.dev/blog/2024/04/25/react-19"><code>useFormState</code> is being renamed <code>useActionState</code></a>. As of the time of writing that's still in RC. Perhaps I'll remember to come back and update this post when it's released. But perhaps I won't.</p>
<h2>What about the API?</h2>
<p>It has been the case for quite some time that the greatest value in a web application is often not found in its UI but in its API. The UI is just a way to interact with the API, and the API is where the real work gets done. If your application is genuinely useful to other people, there's a good chance they will want to integrate with it via an API.</p>
<p>There is a school of thought that says your UI should be treated just the same as any other API client for your system. This is a good school, and its teachers are worth listening to. UIs are for humans and APIs are for machines, but there's a lot of overlap in what they want in life:</p>
<ul>
<li>A speedy response</li>
<li>To know if their action succeeded, or why it failed</li>
<li>To get the data they asked for, in a format they can easily consume</li>
</ul>
<p>Can't we service them both with the same code? Yes, we can. But it's not always as simple as it seems.</p>
<h3>The real world spoils the fun</h3>
<p>Way up in that second example snippet, we were making a <code>POST</code> request to <code>/api/devices</code>; our UI code was talking to the exact same API endpoint that any other API user would be talking to. There are many obvious benefits to this, mostly centering around the fact that you don't need to maintain parallel code paths for UI and API users. I've worked on systems that did that, and it can end up doubling your codebase.</p>
<p>Server Actions are great, but they take us away from HTTP and REST, which are bedrock technologies for APIs. It's very easy to spam together a bunch of Server Actions for your UI, and then find yourself in a mess when you need to build an API for someone else to use.</p>
<p>The reality is that although API users and UI users do have a lot in common, they also have differences. In our Server Action examples above we were returning a simple object with a <code>status</code> and a <code>message</code>, but in a real API you would likely want to return a more structured response, with an HTTP status code, headers, and a body. We're also much more likely to need things like rate limiting for our API users, which we didn't have to think about for our UI users.</p>
<p>Consider a super simple POST endpoint in a real API. Assume you're using Prisma and Zod for validation - a fairly common pairing. Here's how you might write that API endpoint:</p>
<pre><code class="language-app/api/devices/route.ts">export async function POST(req: NextRequest) {
  try {
    const body = await req.json();

    const data = {
      type: body.type,
      hostname: body.hostname,
      credentials: body.credentials,
    } as Prisma.DeviceCreateInput;

    DeviceSchema.parse(data);
    const device = prisma.device.create({ data });

    return NextResponse.json(device, { status: 201 });
  } catch (error) {
    if (error instanceof ZodError) {
      return NextResponse.json({ error: { issues: error.issues } }, { status: 400 });
    }
    return NextResponse.json({ error: "Failed to create device" }, { status: 500 });
  }
}
</code></pre>
<p>This API endpoint consumes JSON input (assume that auth is handled via middleware), validates it with Zod, and then creates a new device in the database. If the input is invalid, it returns a 400 status code with an error message. If the input looks good but there's an error creating the device, it returns a 500 status code with an error message. If everything goes well, it returns a 201 status code with the newly created device.</p>
<p>Now let's see how we might write a Server Action for the same functionality:</p>
<pre><code class="language-app/actions/devices.ts">'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
  try {
    const data = {
      type: formData.get("type"),
      hostname: formData.get("hostname"),
      credentials: formData.get("credentials"),
    } as Prisma.DeviceCreateInput;

    DeviceSchema.parse(data);
    const device = prisma.device.create({ data });

    revalidatePath("/devices");

    return {
      success: true,
      message: "Device Created Successfully",
      device,
    };
  } catch (error) {
    if (error instanceof ZodError) {
      return {
        success: false,
        message: "Validation Error",
        error: {
          issues: error.issues,
        },
      };
    }

    return {
      success: false,
      message: "Failed to create device",
      error: JSON.stringify(error),
    };
  }
}
</code></pre>
<p>The core of these 2 functions is the same exact 2 lines - one to validate using zod, the other to persist using Prisma. The flow is exactly the same, but in one case we're grabbing JSON, in the other reading form data. In one case we're returning NextResponse objects with HTTP status codes, in the other we're returning objects with <code>success</code> and <code>message</code> keys. The Server Action can also take advantage of nice things like <code>revalidatePath</code> to trigger a revalidation of the page that called it, but we don't want that line in our API endpoint.</p>
<p>Somewhere along the line we will want to show a message to the UI user telling them what happened - hence the <code>message</code> key in the Server Action (the API user can just read the HTTP status code). We could have moved that logic to the UI instead, perhaps returning a <code>statusCode</code> key in the JSON response to emulate an HTTP status code. But that's just reimplementing part of HTTP, and moving the problem to the client, which now has to provide the mapping from a status code to a message. It also means a bigger bundle if we want to support internationalization for those messages.</p>
<p>What this all means is that if you want to take advantage of the UI code cleanliness benefits that come from using Server Actions, and your application conceivably might need an API now or in the future, you need to think about how you are going to avoid duplicating logic between your Server Actions and your API endpoints. This may be a hard problem, and there's no one-size-fits-all solution. Yes you can pull those 2 lines of core logic out into a shared function, but you're still left with a lot of other almost-the-same-but-not-quite code.</p>
<p>Ultimately, it probably just requires another layer of indirection. What that layer looks like will depend on your application, but it's something to think about before you go all-in on Server Actions.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/nextjs-server-action-hero-image.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Avoiding Catastrophe by Automating OPNSense Backups]]></title>
            <link>https://edspencer.net//2024/5/28/automating-opnsense-backups</link>
            <guid>automating-opnsense-backups</guid>
            <pubDate>Tue, 28 May 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>tl;dr:</strong> a Backups API exists for OPNSense. <a href="https://github.com/edspencer/opnsense-autobackup">opnsense-autobackup</a> uses it to make daily backups for you.</p>
<p>A few months ago I set up OPNSense on my home network, to act as a firewall and router. So far it's been great, with a ton of benefits over the eero mesh system I was replacing - static DHCP assignments, pretty local host names via Unbound DNS, greatly increased visibility and monitoring possibilities, and of course manifold security options.</p>
<p>However, it's also become a victim of its own success. It's now so central to the network that if it were to fail, most of the network would go down with it. The firewall rules, VLAN configurations, DNS setup, DHCP etc are all very useful and very endemic - if they go away most of my network services go down: internet access, home automation, NAS, cameras, more.</p>
<p>OPNSense lets you download a backup via the UI; sometimes I remember to do that before making a sketchy change, but I have once wiped out the box without a recent backup, and ended up spending several hours getting things back up again. That was before really embracing things like local DNS and static DHCP assignments, which I now have a bunch of automation and configuration reliant on.</p>
<p>OPNSense has a built-in way to have backups be <a href="https://docs.opnsense.org/manual/how-tos/cloud_backup.html">automatically created and uploaded to a Google Drive folder</a>. Per the docs it does this on a daily basis, uploading a new backup to Google Drive if something changed. If you want to use Google Drive for your backup storage, this is probably the right option for you, but if you want to customize how this works - either the schedule on which backups are made, or where they're sent, there are ways to do that too.</p>
<h2>Using the OPNSense API to create backups</h2>
<p>OPNSense provides a simple API that allows you to download the current configuration as an XML file. It gives you the same XML file that you get when you click the "Download configuration" button manually in the OPNSense UI. It's worth downloading it manually once and just skimming through the file in your editor - it's nicely organized and interesting to peruse.</p>
<p>Once you've done that, though, you'll probably want to automate the process so you don't have to remember. That's fairly straightforward:</p>
<h3>Setting up OPNSense for API backups</h3>
<p>We need to set up a way to access the OPNSense backups API, ideally not using our root user - or indeed any user with more access privileges than necessary to create backups. To accomplish this we'll set up a new Group called <code>backups</code> - create the Group via the OPNSense UI, then edit it to assign the <code>Diagnostics: Configuration History</code> privilege. This grants access to the /api/core/backup/ APIs.</p>
<p><img src="/images/posts/opnsense-grant-backups-api.png" alt="OPNSense Assign Backups privilege"></p>
<p>Then, create a new User called <code>backup</code>, and add it to the new <code>backups</code> Group. Your Group configuration will end up looking something like this:</p>
<p><img src="/images/posts/opnsense-add-backups-group.png" alt="OPNSense Add Backups Group"></p>
<p>Now that you have a new <code>backup</code> User, which has access only to configuration/backups APIs, you need to generate an API Key and Secret. Do this in the UI (your actual key will be a long random string):</p>
<p><img src="/images/posts/opnsense-user-create-key.png" alt="OPNSense Create User Key &#x26; Secret"></p>
<p>Creating an API Key for the user will automatically initiate a download in your browser of a text file containing 2 lines - the key itself and a secret. This is the one and only time you will be able to gain access to the secret, so save it somewhere. An encrypted version of it will be kept in OPNSense, but you'll never be able to get hold of the non-encrypted version again if you lose it. Here's what the text file will look like:</p>
<pre><code class="language-shell">key=SUPER+TOP+SECRET+KEY
secret=alongstringofrandomlettersandnumbers
</code></pre>
<h2>Downloading a backup via the API</h2>
<p>Let's test out our new user with a curl command to download the current configuration. The <code>-k</code> tells curl to disregard the fact that OPNSense is likely to respond with an SSL certificate curl doesn't recognize (for your home network you are unlikely to care too much about this). The <code>-u</code> sends our new user's API Key and Secret using HTTP Basic auth:</p>
<pre><code class="language-shell">$ curl -k -u "SUPER+TOP+SECRET+KEY":"alongstringofrandomlettersandnumbers" \
 https://firewall.local/api/core/backup/download/this > backup

$ ls -lh
total 120
-rw-r--r--  1 ed  staff    56K May 24 09:33 backup
</code></pre>
<p>Cool - we have a 56kb file called backup, which ends up looking something like this:</p>
<pre><code class="language-xml">&#x3C;?xml version="1.0"?>
&#x3C;opnsense>
  &#x3C;theme>opnsense&#x3C;/theme>
  &#x3C;sysctl>
    &#x3C;item>
      &#x3C;descr>Increase UFS read-ahead speeds to match the state of hard drives and NCQ.&#x3C;/descr>
      &#x3C;tunable>vfs.read_max&#x3C;/tunable>
      &#x3C;value>default&#x3C;/value>
    &#x3C;/item>
    &#x3C;item>
      &#x3C;descr>Set the ephemeral port range to be lower.&#x3C;/descr>
      &#x3C;tunable>net.inet.ip.portrange.first&#x3C;/tunable>
      &#x3C;value>default&#x3C;/value>
    &#x3C;/item>
    &#x3C;item>
      &#x3C;descr>Drop packets to closed TCP ports without returning a RST&#x3C;/descr>
      &#x3C;tunable>net.inet.tcp.blackhole&#x3C;/tunable>
      &#x3C;value>default&#x3C;/value>

... 1000 more lines of this ...

&#x3C;/opnsense>
</code></pre>
<p>In my case I have a couple of thousand lines of this stuff - you may have more or less. Obviously, we wouldn't usually want to do this via a curl command, especially not one that resulted in our access credentials finding their way into our command line history, so let's make this a little bit better.</p>
<h2>Automating it all</h2>
<p>There are a variety of options here, on 2 main axes:</p>
<ul>
<li>Where to send your backups</li>
<li>How often to make a backup</li>
</ul>
<p>In my case I want to put the file into a git repository, along with other network configuration files. OPNSense does have a <a href="https://docs.opnsense.org/manual/git-backup.html#concept">built-in way to back up files to a git repo</a>, but I want to be able to put more than just OPNSense config files in this repo, so I went for a more extensible approach.</p>
<p>Daily backups seem reasonable here, as well as the option to create them ad-hoc. Ideally one would just run a single script and a timestamped backup would appear in a backups repo. As I recently set up TrueNAS scale on my local network, this seemed a great place to host a schedulable Docker image, so that's what I did.</p>
<p>The Docker image in question handles downloading the backups and pushing them to a GitHub repository. This approach allows us to easily schedule and manage our backups using TrueNAS SCALE, or anywhere else on the network you can run a docker container. It's published as <a href="https://hub.docker.com/repository/docker/edspencer/opnsense-autobackup/general">edspencer/opnsense-autobackup</a> on Docker Hub, and the source code is up at https://github.com/edspencer/opnsense-autobackup.</p>
<h3>Setting Up the Docker Container on TrueNAS SCALE</h3>
<p>Here’s a quick walkthrough on how to set up the Docker container on TrueNAS SCALE and configure it to automate your OPNSense backups.</p>
<h4>Prerequisites</h4>
<ol>
<li><strong>Docker Installed on TrueNAS SCALE</strong>: Ensure that Docker is installed and running on your TrueNAS SCALE system.</li>
<li><strong>GitHub Repository</strong>: Create a GitHub repository to store your backups.</li>
<li><strong>GitHub Personal Access Token</strong>: Generate a GitHub personal access token with repo read/write permissions to allow the Docker container to push to your repository.</li>
</ol>
<h4>Generate a GitHub Personal Access Token</h4>
<ol>
<li>Go to <a href="https://github.com/settings/tokens">GitHub Settings</a>.</li>
<li>Click on <strong>Generate new token</strong>.</li>
<li>Give your token a descriptive name and give it read and write permissions for your new backups repository</li>
<li>Click <strong>Generate token</strong>.</li>
<li>Copy the token and save it securely. You will need it to configure the Docker container.</li>
</ol>
<h4>Set Up the Docker Container on TrueNAS SCALE</h4>
<p>Navigate to the Apps screen on the TrueNAS Scale instance, then click <code>Discover Apps</code> followed by <code>Custom App</code>. Give your app a name and set it to use the <em>edspencer/opnsense-autobackup</em> docker image, using the <code>latest</code> tag.</p>
<p>You'll need to provide the following environment variables, so configure those now in the <em>Container Environment Variables</em> section:</p>
<p>| Name         | Value                                                   |
|--------------|---------------------------------------------------------|
| API_KEY      | <code>your_opnsense_api_key</code>                                 |
| API_SECRET   | <code>your_opnsense_api_secret</code>                              |
| HOSTNAME     | <code>firewall.local</code>                                        |
| GIT_REPO_URL | <code>https://github.com/your_username/your_repo.git</code>        |
| GIT_USERNAME | <code>your_git_username</code>                                     |
| GIT_EMAIL    | <code>your_git_email</code>                                        |
| GIT_TOKEN    | <code>your_git_token</code>                                        |
| CRON_SCHEDULE| <code>0 0 * * *</code>                                             |</p>
<p>Set the <code>CRON_SCHEDULE</code> to anything you like - this one will make it run every day at midnight UTC. Click <code>Install</code> to finish, and you should see the app up and running. So long as you have created your GitHub repo and PAT, you should already see your first backup files in your repo. Depending on what you set for your <code>CRON_SCHEDULE</code>, you'll see new files automatically appearing as long as the image is running.</p>
<p>And you should see some Docker log output like this:</p>
<pre><code class="language-shell">2024-05-25 09:58:05.362503-07:00CRON_SCHEDULE provided: 0 * * * *. Setting up cron job...
2024-05-25 09:58:07.707058-07:00Starting cron service...
2024-05-25 09:58:07.707137-07:00Starting backup process...
2024-05-25 09:58:07.708367-07:00Cloning the repository...
2024-05-25 09:58:07.710068-07:00Cloning into '/repo'...
2024-05-25 09:58:08.339297-07:00Downloading backup...
2024-05-25 09:58:08.343397-07:00% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
2024-05-25 09:58:08.343461-07:00Dload  Upload   Total   Spent    Left  Speed
2024-05-25 09:58:08.379857-07:000     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 57117  100 57117    0     0  1521k      0 --:--:-- --:--:-- --:--:-- 1549k
2024-05-25 09:58:08.381179-07:00Saving backup as latest.xml and opnsense_2024-05-25_16-58.xml...
2024-05-25 09:58:08.391197-07:00[main 7922900] Backups generated 2024-05-25_16-58
2024-05-25 09:58:08.391785-07:001 file changed, 1650 insertions(+)
2024-05-25 09:58:08.391814-07:00create mode 100644 opnsense_2024-05-25_16-58.xml
2024-05-25 09:58:09.087436-07:00To https://github.com/edspencer/opnsense-backups.git
2024-05-25 09:58:09.087476-07:00bce0d8a..7922900  main -> main
2024-05-25 09:58:09.090436-07:00Backup process completed.
</code></pre>
<h3>Conclusions and Improvements</h3>
<p>I feel much safer knowing that OPNSense is now being continually backed up. There are a bunch of other heavily-configured devices on my network that I would like centralized daily backups for - Home Assistant and my managed switch configs being the obvious ones. More to come on those.</p>
<p>Obviously you could run this anywhere, not just in TrueNAS, but I like the simplicity, observability and resource reuse of using the TrueNAS installation I already set up. So far that's working out well, though it use some monitoring and alerting in case it stops working.</p>
<p>For a detailed guide on setting up the Docker container and automating your backups, visit the <a href="https://github.com/edspencer/opnsense-autobackup">GitHub repository</a>. The <a href="https://github.com/edspencer/opnsense-autobackup/blob/main/entrypoint.sh">script that actually gets run</a> is super simple, and easily adaptable to your own needs.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/opnsense-autobackup-logo.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Automating a Home Theater with Home Assistant]]></title>
            <link>https://edspencer.net//2024/1/25/automating-a-home-theater-with-home-assistant</link>
            <guid>automating-a-home-theater-with-home-assistant</guid>
            <pubDate>Thu, 25 Jan 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>I built out a home theater in my house a couple of years ago, in what used to be a bedroom. From the moment it became functional, we started spending most evenings in there, and got into the rhythm of how to turn it all on:</p>
<ul>
<li>Turn on the receiver, make sure it's on the right channel</li>
<li>Turn on the ceiling fan at the right setting</li>
<li>Turn on the lights just right</li>
<li>Turn on the projector, but not too soon or it will have issues</li>
<li>Turn on the Apple TV, which we consume most of our content on</li>
</ul>
<p>Not crazy difficult, but there is a little dance to perform here. Turning on the projector too soon will make it never be able to talk to the receiver for some reason (probably to prevent me from <a href="https://www.youtube.com/watch?v=ALZZx1xmAzg">downloading a car</a>), so it has to be delayed the right number of seconds otherwise you have to go through a lengthy power cycle to get it to work.</p>
<p>It also involves the location of no fewer than 5 remote controls, 3 of which use infrared. The receiver is hidden away in a closet, so you had to go in there to turn that on, remote control or no. Let's see if we can automate this so you can turn the whole thing on with a single button.</p>
<h2>IR is not a good solution</h2>
<p>The first thing I tried were these <a href="https://amzn.to/3K7q4Wt">IR Repeaters</a>, which I figured would allow me to keep the receiver remote in the theater and not have to go into the closet. I tried a few different models but they were all super weak for some reason, despite being plugged in, to the extent that you need to position the re-emitter within inches of the device's IR sensor. I couldn't achieve that in a way that wasn't ugly with wires hanging everywhere, so I gave up on that idea.</p>
<p>Then I tried these <a href="https://amzn.to/44Pn2Qn">Bestcon IR Blaster</a> things, which in theory allow you to record remote control buttons and repeat them. The IR Blaster can join your network, which means it can be automated using Home Assistant, which I use extensively around the house already. I planned to place one of these in the theater itself (for the projector) and another in the closet (for the receiver).</p>
<p>This kinda worked, but it was a bit of a pain to program and they just weren't reliably triggering the devices. Significantly more than zero percent of the time the signal didn't get through, and as with the IR repeaters, you end up with more wires hanging around as it still relies on line-of-sight to the IR sensor. It's also another moving part, something to go wrong, and another random device on your network so it seems this has more downsides than up.</p>
<p>Finally, the coup de grace was that the IR Blaster doesn't know what state your devices are in (bad if you're trying to turn on your device, it's already on, and now you just toggled it off), nor does it know if the command it tried to send was received or needs to be re-tried. There must be a better way...</p>
<h2>TCP/IP to the rescue</h2>
<p>It turns out all of the devices I wanted to control were network-connectable. In the case of the receiver (<a href="https://amzn.to/3ypPPPf">a Denon AVR-X6700H</a>) and the projector (<a href="https://amzn.to/44JAXaB">a Sony VPL-VW325ES</a>), there's an ethernet port that lets you plug it directly into your network.</p>
<p>Both of these devices actually expose a little HTTP server hosting a basic web app. These allow you to both power on/off the device, and do things like change input. The receiver, pleasingly enough, actually publishes a pretty complete API, which allows you to do basically anything you could do with the remote in your hand, including advanced configuration. Awesome.</p>
<p><a href="https://assets.denon.com/documentmaster/uk/avr1713_avr1613_protocol_v860.pdf">The documentation</a> is extensive, though rather dense. Contained therein is the fact that we can send commands like <strong>ZMON</strong> and <strong>SIBD</strong> to the receiver, which will turn it on and <strong>S</strong>witch <strong>I</strong>nput to the <strong>B</strong>luray <strong>D</strong>isk input respectively. As well as the web UI, the receiver exposes a way to send those odd little commands over HTTP - in this case we can just send a GET request to http://receiver.local:8080//goform/formiPhoneAppDirect.xml?ZMON, which will turn on the receiver's main zone. Swap <strong>ZMON</strong> for whatever command you want to run. There's no actual iPhoneApp involved here, but I guess from this url that one exists.</p>
<p>As we'll see in a moment, that's all we need to know to get Home Assistant able to control both of these devices.</p>
<h2>Home Assistant Preparation</h2>
<p>Home Assistant already has built-in support for controlling the Sony Projector, so now that it has an IP on our network we can just tell Home Assistant where to find the Projector. As <a href="https://www.home-assistant.io/integrations/sony_projector/">per the docs</a>, this requires a manual edit to configuration.json, which is unusual but easy enough.</p>
<p>There are several ways to edit that file, the easiest probably being to <a href="https://www.google.com/search?q=home+assistant+install+file+editor&#x26;oq=home+assistant+install+file+editor">use the File Editor addon</a>. Again per the docs, this just means adding lines 12-15 to your configuration.yaml file (replace <em>projector.local</em> with the IP of the projector if you don't have fancy local DNS wizardry running):</p>
<p><img src="/images/posts/home-assistant-projector-switch.png" alt="Home Assistant Projector Switch"></p>
<p>Either restart home assistant or reload the YAML so it can pick that up. Now your projector shows up as a persistent switch in home assistant, so you can turn it on/off at will either via the home assistant UI or via scripts and other automations.</p>
<p>To get Home Assistant able to talk to the receiver, I had to install the Denon AVR integration. That's pretty easy and gives you a pretty basic device page for the receiver, where you can turn it on/off but not much else:</p>
<p><img src="/images/posts/home-assistant-denon-device.png" alt="Home Assistant Receiver Device"></p>
<p>But it also gives 3 addition services you can call in your automations, one of which is the all-important <em>Denon AVR Network Receivers: Get command</em>.</p>
<h2>The Script</h2>
<p>At this point the script is pretty easy. In order, we:</p>
<ul>
<li>Use that iPhoneAppDirect.xml path to send the <strong>ZMON</strong> command (Zone Main On) to the receiver</li>
<li>Turn on the fan (using the <a href="/2024/01/13/how-to-make-bond-fans-work-with-home-assistant/">Bond integration we fixed last time</a>)</li>
<li>Set the correct lighting scene (all Philips Hue fixtures and LED strips in this case)</li>
<li>Wait the right number of seconds so the projector can talk to the receiver properly</li>
<li>Call the receiver again to switch to the XBox input (<strong>SIBD</strong>)</li>
<li>Call the Switch: turn on service on the Projector entity that we added to <em>configuration.yaml</em></li>
</ul>
<p><img src="/images/posts/home-assistant-theater-on-script.png" alt="Home Assistant Theater On Script"></p>
<p>We switch to the XBox input first in case we're going to watch a Bluray, otherwise we just press any button on the Apple TV remote to wake that device up and the receiver automatically switches to it. There is also a Theater Off script, which basically does the opposite of the above.</p>
<p>An occasionally useful feature is that we can now turn the theater on and off remotely. As the projector does take a couple of minutes to warm up it can be nice to turn it all on with one button on my phone and then waddle over there a couple of minutes later to find everything ready.</p>
<h2>Triggering with a light switch</h2>
<p>As I had switched all of the lights in the room to be various Philips Hue fixtures and light strips, the 2-gang light switch box by the entry door suddenly became redundant as all of the lights were permanently powered. This gave a delightful opportunity to install on and off switches in their stead:</p>
<p>These two switches are not connected to any power source, but to a <a href="https://amzn.to/4dJrSCF">Philips Hue Wall Switch Module</a>, which is just a simple battery-powered device that detects when you flip the switch and exposes that event to the rest of the Hue ecosystem. Because Hue integrates well with Home Assistant, that means we can trivially use it as a trigger for our automations.</p>
<p>The Hue wall module approach works well for this, even though it's not really what it's designed for. All it does is track when a switch is flipped - it doesn't know whether it's on or off, doesn't stop you from flipping it several times (though Home Assistant can dedupe that if necessary), and some day the battery will need to be replaced, but it's served as an excellent solution for us. It also means guests don't have to figure out how to turn everything on/off correctly - just flip the switch.</p>
<h2>Possible Extensions &#x26; Limitations</h2>
<p>Home Assistant can also integrate more deeply with XBox and Apple Tv. In the case of XBox, this requires you to switch it into a much more power-hungry standby mode, which would have the device consuming 30 watts in standby. That's a huge amount of power to spend to basically just enable HA to turn it on and off, so I passed on that.</p>
<p>Similarly, HA can integrate more deeply with Apple TV - loading content as well as just turning it on/off. But, as we use a variety of different apps, the integration wouldn't have much of a chance of knowing which one we're going to choose, so while there's no real power consumption downside, it just wouldn't be useful in our case.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/theater-of-screams.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[How to make Bond fans work better with Home Assistant]]></title>
            <link>https://edspencer.net//2024/1/13/how-to-make-bond-fans-work-with-home-assistant</link>
            <guid>how-to-make-bond-fans-work-with-home-assistant</guid>
            <pubDate>Sat, 13 Jan 2024 06:31:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>I have a bunch of these nice <a href="https://amzn.to/4buXRFi">Minka Aire fans</a> in my house:</p>
<p><img src="/images/posts/minka-aire-fan.jpg" alt=""></p>
<p>They're nice to look at and, crucially, silent when running (so long as the screws are nice and tight). They also have some smart home capabilities using the Smart by Bond stack. This gives us a way to integrate our fans with things like Alexa, Google Home and, in my case, <a href="https://www.home-assistant.io/">Home Assistant</a>.</p>
<h2>Connecting Bond with Home Assistant</h2>
<p>In order to connect anything to these fans you need a <a href="https://amzn.to/4dPJRaH">Bond WIFI bridge</a>. This is going to act as the bridge between your fans and your network. Once you've got it set up and connected to your wifi network, you'll need to figure out what IP it is on. You can send a curl request to the device to get the Access Token that you will need to be able to add it to Home Assistant:</p>
<p><img src="/images/posts/bond-get-token.png" alt="curl command"></p>
<p>If you get an access denied error, it's probably because the Bond bridge needs a proof of ownership signal. The easiest way to do that is to just power off the bridge and power it on again - run the curl again within 10 minutes of the bridge coming up and you'll get your token.</p>
<p>Integrating Bond with Home Assistant is then pretty easy - search for the Bond integration at http://homeassistant.local:8123/config/integrations/dashboard (substitute for your Home Assistant domain if different) and install, providing the IP and Token you have for your Bond bridge:</p>
<p><img src="/images/posts/bond-add-to-home-assistant.png" alt="add Bond integration"></p>
<p>It will populate your fans - here's an example, the fan in my home theater:</p>
<p><img src="/images/posts/bond-device-home-assistant.png" alt="add Bond integration"></p>
<p>The top 2 controls there in theory control the fan and the fan's light.</p>
<h2>The Annoying Light Toggle bug</h2>
<p>Sometimes the light on the fan gets turned on and is impossible to turn off. Whether you use the remote control, the Bond app or Home Assistant, no force in the known universe will turn the fan light off. It's really annoying when it happens. The only way to fix it is to turn the fan off and on again at the breaker, after which it will start responding to commands again.</p>
<p>It also seems to be implemented as a memory-less toggle in some contexts, and a dimmable light in others, and Bond/Home Assistant don't necessarily know the current true state of the light. The Bond app even has a settings page called "Fix Tracked State", where you can go to manually override what Bond thinks is the current light state, assuming it has wandered out of sync. However, even after toggling this in all the ways I could, the bug persisted and it still needed a visit to the breaker box.</p>
<p><img src="/images/posts/bond-fix-tracked-state.png" alt="Bond app fix state"></p>
<p>One annoying way this bug manifests itself is that the fan lights will turn on when I run my <em>"All Lights Off"</em> script on Home Assistant - this script calls the <strong>light.turn_off</strong> service on all of the lights set up in the following areas of the house. Curiously, this turns ON the fan lights. I guess that's just because Bond doesn't know if the light is on or off, so it just tries to toggle.</p>
<p><img src="/images/posts/home-assistant-all-lights-off-script.png" alt="Bond app fix state"></p>
<p>Given that one of these fans is in the bedroom and I press a button that runs the script when we're ready to sleep, it's a little unfortunate that the <em>"All Lights Off"</em> script ends up turning on a bright fan light. Doubly so when I have to walk to the garage to power cycle the breaker to be able to turn the light off again. We need a solution here.</p>
<h2>Home Assistant - disable the entity</h2>
<p>In my case, as I'm using Home Assistant for basically all of the automations in the house, and there is never a time when I want to turn the fan light on, I just disabled it in Home Assistant. There are a few ways to do this but one is to use the <a href="http://homeassistant.local:8123/config/entities">Entites View in Home Assistant</a> and search for "fan". Click the light for fan in question (the row with the lightbulb icon), then the cog in the top right of the modal window and uncheck the "Enabled" flag:</p>
<p><img src="/images/posts/home-assistant-disable-fan-light.png" alt="Bond app fix state"></p>
<p>Now back on your device page the control for the fan light will have disappeared and it'll tell you that an entity is not being shown. Now, calls to <strong>light.turn_off</strong> won't target the fan's light, and therefore won't turn it on when you don't want it to. Your scripts can still turn the fan itself on/off and set the speed though.</p>
<p><img src="/images/posts/home-assistant-entity-disabled.png" alt="Bond app fix state"></p>
<p>Although we lose the ability to control the fan light by doing this, that's not why I have the fans so I don't really care. We have other lighting in the rooms with those fans, so the fan light is never used. Their value is in their silence and prettiness. It's awesome that they integrate with things like Home Assistant. Hopefully this helps out others who have run into similar problems.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/minka-aire-fan.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Demystifying OpenAI Assistants - Runs, Threads, Messages, Files and Tools]]></title>
            <link>https://edspencer.net//2023/11/17/demystifying-openai-assistants-runs-threads-messages-files-and-tools</link>
            <guid>demystifying-openai-assistants-runs-threads-messages-files-and-tools</guid>
            <pubDate>Fri, 17 Nov 2023 03:11:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>As I mentioned in <a href="https://edspencer.net/2023/11/15/using-chatgpt-to-generate-chatgpt-assistants/">the previous post</a>, OpenAI dropped a ton of functionality recently, with the shiny new <a href="https://platform.openai.com/docs/api-reference/assistants">Assistants API</a> taking center stage. In this release, OpenAI introduced the concepts of <a href="https://platform.openai.com/docs/api-reference/threads">Threads</a>, <a href="https://platform.openai.com/docs/api-reference/messages">Messages</a>, <a href="https://platform.openai.com/docs/api-reference/runs">Runs</a>, <a href="https://platform.openai.com/docs/api-reference/files">Files</a> and <a href="https://platform.openai.com/docs/assistants/tools">Tools</a> - all higher-level concepts that make it a little easier to reason about long-running discussions involving multiple human and AI users.</p>
<p>Prior to this, most of what we did with OpenAI's API was call the chat completions API (setting all the non-text modalities aside for now), but to do so we had to keep passing all of the context of the conversation to OpenAI on each API call. This means persisting conversation state on our end, which is fine, but the Assistants API and related functionality makes it easier for developers to get started without reinventing the wheel.</p>
<h2>OpenAI Assistants</h2>
<p>An <a href="https://platform.openai.com/docs/assistants/how-it-works">OpenAI Assistant</a> is defined as an entity with a name, description, instructions, default model, default tools and default files. It looks like this:</p>
<p><img src="/images/posts/assistant.png" alt=""></p>
<p>Let's break this down a little. The name and description are self-explanatory - you can change them later via the <a href="https://platform.openai.com/docs/api-reference/assistants/modifyAssistant">modify Assistant API</a>, but they're otherwise static from Run to Run. The model and instructions fields should also be familiar to you, but in this case they act as defaults and can be easily overridden for a given Run, as we'll see in a moment.</p>
<p><a href="https://platform.openai.com/docs/assistants/tools">Tools</a> needs a little more explanation. Tools refers to the set of optional capabilities that can be enabled for the Assistant, but they can also be overridden for a particular run. There are 2 broad types of Tool - OpenAI-hosted and self-hosted. At the moment there are 2 OpenAI-hosted tools - Code Interpreter and Retrieval. To allow your Assistant to write and run code to solve problems, you must enable Code Interpreter; to allow it to look at files you give it, you must enable Retrieval. I suspect this category of tools will just be switched on by default in the future, but for now you have to do it yourself.</p>
<p>The second set of tools are your Custom Functions. <a href="https://edspencer.net/2023/11/15/using-chatgpt-to-generate-chatgpt-assistants/">I discussed these a little in the last post</a> - basically it's just a way to tell the Assistant about functions you have in your codebase that you would like it to be able to invoke (albeit not directly - <a href="https://edspencer.net/2023/11/15/using-chatgpt-to-generate-chatgpt-assistants/">read the previous post for more</a>). These are just JSON definitions of the names and shapes of your functions - there's no actual code being sent or run there.</p>
<p>Tools, therefore, means zero or more of your own Custom Functions, plus Retrieval and/or Code Interpreter, if you want to enable them. Tools can be defined at Assistant creation-time, but can be overridden at Run creation-time.</p>
<p>Finally, let's examine <a href="https://platform.openai.com/docs/api-reference/files">Files</a>. Files are actually their own top-level concept; once you upload a File you can then link it to Assistants or Messages - under the covers there are <a href="https://platform.openai.com/docs/api-reference/assistants/file-object">AssistantFile</a> and <a href="https://platform.openai.com/docs/api-reference/messages/file-object">MessageFile</a> objects that allow there to be a many-to-many relationship between Assistants and Files. Again, Files you make available to your Assistant at creation-time can be overridden at Run-time.</p>
<h2>Threads and Messages</h2>
<p>A <a href="https://platform.openai.com/docs/api-reference/threads">Thread</a> is just an ordered array of <a href="https://platform.openai.com/docs/api-reference/messages">Messages</a>. A Message has a role (either "user" or "assistant" - human or machine), some content (what the user said) and an optional set of Files. As before, the Files are linked to the message via an underlying MessageFile, so Files can be reused between Assistants and Messages.</p>
<p><img src="/images/posts/thread.png" alt=""></p>
<p>In this example we have a Thread with 4 Messages. The first two are from human participants in the Thread, perhaps Bob is asking for some calendar and product data, so Fred (another human) sends it, along with whatever message content he wrote. But there are also 2 Assistants in the Thread - imaginatively named Assistant 1 and Assistant 2, who wrote Message 3 and Message 4 respectively. In order for these two Messages to be created and added to the Thread, the Assistants will need to be invoked via a Run.</p>
<h2>So What's a Run?</h2>
<p>A <a href="https://platform.openai.com/docs/api-reference/runs">Run</a> is an entity that represents the process of invoking an Assistant on a Thread. Only one Run can be executing at a time for a given Thread. The Run configuration declares which Assistant should be invoked, what Thread ID to use, and then a bunch of familiar-looking optional parameters. For example, you can define the instructions for the Assistant when you create the Assistant itself, but you can also override them for the specific Run:</p>
<p><img src="/images/posts/run.png" alt=""></p>
<p>You probably noticed that the Run diagram looks a lot like the Assistant diagram. Most of the stuff you can define on the Assistant can be overridden at Run creation-time. You can even change which model the Assistant uses during the Run, which feels a little odd and probably isn't something you'd do too often, but at the end of the day it's just swapping one text-in-text-out function call for another so why not - see the final paragraph of this post for why this might be.</p>
<p>Although you can set your Assistant up with Tools and Files, you can also override those at Run creation-time. It's nice to have that flexibility, though I think it's easier to reason about Assistant capabilities than Run-specific Assistant capabilities, so I suspect most use cases will not involve overriding Tools and Files at Run-creation time. You are currently limited to 20 Files per Assistant, with some size limits too, so the Run-specific overriding of Files would be a way to have your Assistants operate on more than 20 files during the Thread lifetime. That's a slightly hacky way around what is probably a short-term limitation though.</p>
<h2>Tracing Runs across a Thread</h2>
<p>Returning to our Thread 123 example a couple of pictures up, let's take a look at the Runs that were invoked against our Thread. In the image below we have 3 runs - the last one is a bonus Run against a hypothetical Message 5 in our Thread, showing that you can override basically everything an Assistant is on the Run itself.</p>
<p>Run 1 was created against our Thread 123 at some point after Bob and Fred had sent their Messages (Message 1 and Message 2). Run 1 is super basic - it just defines the Assistant to use (Assistant 1) and the Thread to execute on (Thread 123 - the same for all of these Runs). Its execution yields Message 3, which is added to the Thread.</p>
<p>We then triggered Run 2, this time asking Assistant 2 to provide its input, as well as overriding both the model and instructions for Assistant 2, and providing a custom set of Tools. This yields Message 4, which completes the Thread example above.</p>
<p><img src="/images/posts/runsexample-2.png" alt=""></p>
<p>Run 3 is just to show what a next Run invocation might look like, customizing Files, Tools, model and instructions. At this point, you're arguably not using the Assistants API at all as everything in your Assistant has been overridden.</p>
<p>Bear in mind that each Run has to be triggered by something - it won't happen automatically by Messages being appended to a Thread, so you need something that actually kicks this off. One challenge in Threads that involve multiple human and Assistant users is figuring out when to invoke which Assistant - I'll have some more thoughts on that in an upcoming post.</p>
<h2>A Simplified Conceptual Model</h2>
<p>Let's close out with a simplified diagram of the relationships between the actors in this play. On the right we find the Assistant, configured with its default Files and Tools. It is also tied to a set of Runs, as each Run is executed against a single Assistant. There's a one-to-many relationship between the Assistant and its Runs, though these Runs could be against more than one Thread.</p>
<p>On the other side of the diagram, we see that a Thread is composed of multiple Messages, which can be added to later, and that a Thread also has multiple Runs associated with it. Messages can have message-specific Files attached in addition to their content.</p>
<p>Finally, the glue holding it all together in the center is the Run, which executes on specific Thread using a specific Assistant, but can also provide Run-specific Files and Tools to make available to the Assistant during the invocation. Usually a new Message will be appended to the Thread as a result of the invocation, but the <a href="https://platform.openai.com/docs/assistants/how-it-works/runs-and-run-steps">Run lifecycle</a> is a little deeper than that and worthy of further examination in another post.</p>
<p><img src="/images/posts/assistantdatamodel.png" alt=""></p>
<p>Although there are implied one-to-many relationships between Assistant and Run, and between Thread and Run, there is currently no way to get all of the Runs for a given Thread [<a href="https://platform.openai.com/docs/api-reference/runs/listRuns">UPDATE: listRuns API now does this</a>] or for a given Assistant, so if you want to track the state of a Run currently executing on a Thread, you need to keep track of both the Thread ID and the Run ID to be able to use the <a href="https://platform.openai.com/docs/api-reference/runs/getRun">getRun API</a> to get the Run status. I imagine this will change in the near future.</p>
<p>This is definitely progress in terms of making it easier for developers to build persistent generative AI applications with a chat component, though it looks like this is all just an abstraction placed over the same old underlying LLM text-in-text-out function. That's not to say that abstractions like this are not a very welcome thing, just bear in mind what's really happening under the covers.</p>
<p>Looking at the picture above it's fairly easy to see how the set of Messages, Files and Tools (the Custom Function definitions at least) in a Thread could be smushed together into a big ole blob of text and fed to the LLM, probably stitched inside some other prompt text. This is why it's reasonable (though probably not all that useful) to swap out the model between Runs - at the end of the day we're just passing a bunch of text into a function called an LLM and getting some text out of it.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/assistant.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Using ChatGPT to generate ChatGPT Assistants]]></title>
            <link>https://edspencer.net//2023/11/15/using-chatgpt-to-generate-chatgpt-assistants</link>
            <guid>using-chatgpt-to-generate-chatgpt-assistants</guid>
            <pubDate>Wed, 15 Nov 2023 02:02:48 GMT</pubDate>
            <content:encoded><![CDATA[<p>OpenAI dropped a ton of cool stuff in their <a href="https://www.youtube.com/watch?v=pq34V_V5j18">Dev Day presentations</a>, including some updates to function calling. There are a few function-call-like things that currently exist within the Open AI ecosystem, so let's take a moment to disambiguate:</p>
<ul>
<li><strong>Plugins</strong>: introduced in March 2023, allowed GPT to <strong>understand and call</strong> your HTTP APIs</li>
<li><strong>Actions</strong>: an evolution of Plugins, makes it easier but still calls your HTTP APIs</li>
<li><strong>Function Calling</strong>: Chat GPT understands your functions, tells you how to call them, but <strong>does not actually call them</strong></li>
</ul>
<p>It seems like Plugins are likely to be superseded by Actions, so we end up with 2 ways to have GPT call your functions - Actions for automatically calling HTTP APIs, Function Calling for indirectly calling anything else. We could call this Guided Invocation - despite the name it doesn't actually call the function, it just tells you how to.</p>
<p>That second category of calls is going to include anything that isn't an HTTP endpoint, so gives you a lot of flexibility to call internal APIs that never learned how to speak HTTP. Think legacy systems, private APIs that you don't want to expose to the internet, and other places where this can act as a highly adaptable glue.</p>
<p>I've put all the source code for this article up at <a href="https://github.com/edspencer/gpt-functions-example">https://github.com/edspencer/gpt-functions-example</a>, so check that out if you want to follow along. It should just be a matter of following the steps in the README, but YMMV. We are, of course, going to use a task management app as a playground.</p>
<h2>Creating Function definitions</h2>
<p>In order for OpenAI Assistants to be able to call your code, you need to provide them with signatures for all of your functions, in the format that it wants, which look like this:</p>
<pre><code class="language-json">{
  "type": "function",
  "function": {
    "name": "addTask",
    "description": "Adds a new task to the database.",
    "parameters": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "The name of the task."
        },
        "priority": {
          "type": "number",
          "description": "The priority of the task, lower numbers indicating higher priority."
        },
        "completed": {
          "type": "boolean",
          "description": "Whether the task is marked as completed."
        }
      },
      "required": ["name"]
    }
  }
}
</code></pre>
<p>That's pretty self-explanatory. It's also a pain in the ass to keep tweaking and updating as you evolve your app, so let's use the OpenAI Chat Completions API with the <code>json_object</code> setting enabled and see if we can have this done for us.</p>
<h2>Our Internal API</h2>
<p>Let's build a basic Task management app. We'll just use a super-naive implementation of Todos written in TypeScript. My little <a href="https://github.com/edspencer/gpt-functions-example/blob/main/API.ts">API.ts</a> has functions like <code>addTask</code>, <code>updateTask</code>, <code>removeTask</code>, <code>getTasks</code>, etc. All the stuff you'd expect. Some of them take a bunch of different inputs.</p>
<p>Here's a snippet of <a href="https://github.com/edspencer/gpt-functions-example/blob/main/API.ts">our API.ts file</a>. It's very basic but functional, using a sqlite database driven by Prisma:</p>
<pre><code class="language-typescript">interface TaskInput {
  name: string;
  priority?: number;
  completed?: boolean;
  deleted?: boolean;
}

/**
 * Adds a new task to the database.
 * @param taskInput - An object containing the details of the task to be added.
 * @param taskInput.name - The name of the task.
 * @param taskInput.priority - The priority of the task.
 * @returns A Promise that resolves when the task has been added to the database.
 */
async function addTask(taskInput: Task): Promise&#x3C;Task | void> {
  try {
    const task = await prisma.task.create({
      data: taskInput
    })
    console.log(`Task ${task.id} created with name ${task.name} and priority ${task.priority}.`)

    return task;
  } catch (e) {
    console.error(e)
  }
}

/**
 * Updates a task in the database.
 * @param id - The ID of the task to update.
 * @param updates - An object containing the updates to apply to the task.
 * @param updates.name - The updated name of the task.
 * @param updates.priority - The updated priority of the task.
 * @param updates.completed - The updated completed status of the task.
 * @returns A Promise that resolves when the task has been updated in the database.
 */
async function updateTask(id: string, updates: Partial&#x3C;TaskInput>): Promise&#x3C;void> {
  try {
    const task = await prisma.task.update({
      where: { id },
      data: updates,
    })
    console.log(`Task ${task.id} updated with name ${task.name} and priority ${task.priority}.`)
  } catch (e) {
    console.error(e)
  }
}
</code></pre>
<p>It goes on from there. You get the picture. No it's not production-grade code - don't use this as a launchpad for your Todo list manager app. GitHub Copilot actually wrote most of that code (and most of the documentation) for me.</p>
<p>Side note on documentation: it took me more years than I care to admit to figure out that the primary consumer of source code is humans, not machines. The machine doesn't care about your language, formatting, awfulness of your algorithms, weird variable names, etc; algorithmic complexity aside it'll do exactly the same thing regardless of how you craft your code. Humans are a different matter though, and benefit enormously from a little context written in a human language.</p>
<p>Ironically, that same documentation that benefitted human code consumers all this time is now what enables these new machine consumers to grok and invoke your code, saving you the work of coming up with a translation layer to integrate with AI agents. So writing documentation really does help you after all. Also, write tests and eat your vegetables.</p>
<h2>Generating the OpenAI translation layer</h2>
<p>The code to translate our internal API into something OpenAI can use is fairly simple and reusable. All we do is read in a file as text, stuff the contents of that file into a GPT prompt, send that off to OpenAI, stream the results back to the terminal and save it to a file when done:</p>
<pre><code class="language-typescript">/**
 * This file uses the OpenAI Chat Completions API to automatically generate OpenAI Function Call
 * JSON objects for an arbitrary code file. It takes a source file, reads it and passes it into 
 * OpenAI with a simple prompt, then writes the output to another file. Extend as needed.
 */

import OpenAI from 'openai';
import fs from 'fs';
import path from 'path';

import { OptionValues, program } from 'commander';

//takes an input file, and generates a new tools.json file based on the input file
program.option('sourceFile', 'The source file to use for the prompt', './API.ts');
program.option('outputFile', 'The output file to write the tools.json to (defaults to your input + .tools.json');

const openai = new OpenAI();

/**
 * Takes an input file, and generates a new tools.json file based on the input file.
 * @param sourceFile - The source file to use for the prompt.
 * @param outputFile - The output file to write the tools.json to. Defaults to 
 * @returns Promise&#x3C;void>
 */
async function build({ sourceFile, outputFile = `${sourceFile}.tools.json` }: OptionValues) {
  console.log(`Reading ${sourceFile}...`);
  const sourceFileText = fs.readFileSync(path.join(__dirname, sourceFile), 'utf-8');

  const prompt = `
    This is the implementation of my ${sourceFile} file:

    ${sourceFileText}

    Please give me a JSON object that contains a single key called "tools", which is an array of the functions in this file.
    This is an example of what I expect (one element of the array):

    {
      "type": "function",
      "function": {
        "name": "addTask",
        "description": "Adds a new task to the database.",
        "parameters": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "description": "The name of the task."
            },
            "priority": {
              "type": "number",
              "description": "The priority of the task, with lower numbers indicating higher priority."
            },
            "completed": {
              "type": "boolean",
              "description": "Whether the task is marked as completed."
            }
          },
          "required": ["name"]
        }
      }
    },

  `
  //Call the OpenAI API to generate the function definition, and stream the results back
  const stream = await openai.chat.completions.create({
    model: 'gpt-4-1106-preview',
    response_format: { type: 'json_object' },
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  });

  //Keep the new tools.json in memory until we have it all
  let newToolsJson = "";

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || ''
    process.stdout.write(content);
    newToolsJson += content;
  }

  console.log(`Updating ${outputFile}...}`);

  // Write the tools JSON to ../tools.json
  fs.writeFileSync(path.join(__dirname, outputFile), newToolsJson);
}

build(program.parse(process.argv).opts());
</code></pre>
<p>I've made a simple little repo with this file, the API.ts file, and a little demo that shows it all integrated. Run it like this:</p>
<pre><code class="language-bash">ts-node rebuildTools.ts -s API.ts
</code></pre>
<p>Which will give you some output like this, and then update your API.ts.tools.json file:</p>
<pre><code class="language-bash">ts-node rebuildTools.ts -s API.ts          
Reading API.ts...
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "addTask",
        "description": "Adds a new task to the database.",
        "parameters": {
          "type": "object",
          "properties": {
            "name": {

..........truncated...
 full output at https://github.com/edspencer/gpt-functions-example/blob/main/API.ts.tools.json
.............................

        "returns": {
          "type": "Promise&#x3C;void>",
          "description": "A Promise that resolves when all tasks have been deleted from the database."
        }
      }
    }
  ]
}
Updating ./API.ts.tools.json...
Done
</code></pre>
<h2>Creating an OpenAI Assistant and talking to it</h2>
<p>We've had Open AI generate our Tools JSON file, now let's see if it can use it with a simple <code>demo.ts</code>, which:</p>
<ul>
<li><a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L49">Creates a new OpenAI Assistant</a> with some custom instructions</li>
<li><a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L65">Creates a new Thread</a></li>
<li><a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L81">Creates a new Message</a>, attaches it to the Thread</li>
<li><a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L97">Creates a new Run</a> and <a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L148">polls it until complete</a></li>
<li><a href="https://github.com/edspencer/gpt-functions-example/blob/main/demo.ts#L113">Executes any actions</a> that came back from OpenAI</li>
</ul>
<p><a href="https://github.com/edspencer/gpt-functions-example">The code is all up on GitHub</a>, and I won't do a blow-by-blow here but let's have a look at the output when we run it:</p>
<pre><code class="language-bash">ts-node ./demo.ts -m "I need to go buy bread from the store, then go to \
    the gym. I also need to do my taxes, which is a P1."
</code></pre>
<p>And the output:</p>
<pre><code class="language-bash">Creating assistant...
Created assistant asst_hkT3BFQsNf3HSmJpE8KytiX9 with name Task Planner.
Created thread thread_AigYi0oFrytu3aO5k0mRacIV
Retrieved 0 tasks from the database.
Created message
msg_uLpR3UpQB3pX62wVIA7TcqIl
Polling thread
Current status: queued
Trying again in 2 seconds...
Polling thread
Current status: in_progress
Trying again in 2 seconds...
Polling thread
Current status: in_progress
Trying again in 2 seconds...
Polling thread
Current status: requires_action
Actions:
[
  {
    id: 'call_8JX5ffKFpxIhYmJeZYYilpv3',
    type: 'function',
    function: {
      name: 'addTask',
      arguments: '{"name": "Buy bread from the store", "priority": 2}'
    }
  },
  {
    id: 'call_GC4axxSB6Oso0tiolDLr900X',
    type: 'function',
    function: {
      name: 'addTask',
      arguments: '{"name": "Go to the gym", "priority": 2}'
    }
  },
  {
    id: 'call_7c5mWt1I5Ff3h5Lvb0Hfw2L7',
    type: 'function',
    function: {
      name: 'addTask',
      arguments: '{"name": "Do taxes", "priority": 1}'
    }
  }
]
Adding task
Task cloyl2gxs0000c3a7hxe6hupc created with name Buy bread from the store and priority 2.
Adding task
Task cloyl2gxv0001c3a7zi4hqt8z created with name Go to the gym and priority 2.
Adding task
Task cloyl2gxx0002c3a7l0gv7f07 created with name Do taxes and priority 1.
</code></pre>
<p>You can see all of the steps it takes in the console output. We had the creation of the Assistant, the Thread, then we looked to see if our sqlite database has any existing Tasks, in which case we're going to send those along as input too, then we pass those along with the user's message and get back OpenAI's function invocations (3 in this case). Finally, we iterate over them all and call our internal <code>addTask</code> function, and at the bottom of the output we see that our tasks were created successfully.</p>
<p>Let's go call it again, updating the tasks that we just made:</p>
<pre><code class="language-bash">ts-node demo.ts -m "I finished the laundry, please mark it complete. Also the gym is a P1"
</code></pre>
<p>Output:</p>
<pre><code class="language-bash">Creating assistant...
Created assistant asst_WbTXKoXWL1yTWs4zvcVkDIDT with name Task Planner.
Created thread thread_mLvr7acahXbnmoe217f0gMRF
Retrieved 3 tasks from the database.
Created message
msg_iYYkAeuxRPNmJZ5vAKwiI8S7
Polling thread
Current status: queued
Trying again in 2 seconds...
Polling thread
Current status: in_progress
Trying again in 2 seconds...
Polling thread
Current status: in_progress
Trying again in 2 seconds...
Polling thread
Current status: requires_action
Actions:
[
  {
    id: 'call_W4UKGadROhaJJFZym7vQocP7',
    type: 'function',
    function: {
      name: 'completeTask',
      arguments: '{"id": "cloyl2gxs0000c3a7hxe6hupc"}'
    }
  },
  {
    id: 'call_KzaYk1x4sIRFWeKlvgOk37qf',
    type: 'function',
    function: {
      name: 'updateTask',
      arguments: '{"id": "cloyl2gxv0001c3a7zi4hqt8z", "updates": {"priority": 1}}'
    }
  }
]
Completing task
Task cloyl2gxs0000c3a7hxe6hupc marked as completed.
Updating task
Task cloyl2gxv0001c3a7zi4hqt8z updated with name Go to the gym and priority 1.
</code></pre>
<p>That's kinda amazing. All that any of this really does is assemble blobs of text and send them to the OpenAI API, which is able to figure it all out, even with the context of the data, and correctly call both create and update APIs that exist only internally within your system, without exposing anything to the internet at large.</p>
<p>Here it correctly figured out the IDs of the Tasks to update (because I passed that data in with the prompt - it's tiny), which functions to call and that they should be done in parallel, meaning your user can speak/type as much as they like, making a lot of demands in a single submission, and the Assistant will batch it all up into a set of functions that, from its perspective at least, it wants you to run in parallel,</p>
<p>After executing the functions you can send another request to tell the Assistant the outcome - this article is long enough already but you can see how to close that loop on <a href="https://platform.openai.com/docs/guides/function-calling">the OpenAI Function Calling docs</a>.</p>
<h2>Closing Thoughts</h2>
<p>This stuff is all very new, and there are some pros and cons here. While all looks rosy in the end, it did take a few iterations to get GPT to reliably and consistently output the JSON format expected in the translation stage - occasionally it would innovate and restructure things a little, which causes things to break. That's probably just something that time will take care of as this stuff gets polished up, both on OpenAI's end and on everyone else's, but it's something to be aware of.</p>
<p>This technology requires a considered approach to testing too: GPT is a big old black box floating off in the internet somewhere, it's semi-magical, and it doesn't always give the right answer. Bit rot seems a serious risk here - both due to the newness of the tech and the fact that most of us don't really understand it very well. It seems sensible to mock/stub out expected responses from OpenAI's APIs to do unit testing, but when it comes to integration testing, you probably need your tests to do something like what our <code>demo.ts</code> does, and then verify the database was updated correctly at the end.</p>
<p>It can be the case that you make no changes to your code or environment but still get different outcomes due to the non-determinism of GPT. Amelioration for this could be in the form of temperature control and fine tuning, but you're probably going to need to be less than 100% trustful that your Assistant is doing what you think it is.</p>
<p>Finally, there's obviously a huge security consideration here. Fundamentally, we're taking user input (text, speech, images, whatever), and calling code on our own systems as a result. This always involves peril, and one can imagine all kinds of SQL injection-style attacks against Agent systems that inadvertently run malicious actions the developer didn't intend. For example - my <code>API.ts</code> contains a <code>deleteAllTasks</code> function does what you think it does. Because it's part of <code>API.ts</code>, the Assistant knows about it, and could inadvertently call it, whether the user was trying to do that or not.</p>
<p>It would be extremely easy to mix up public and private code in this way and accidentally expose it to the Assistant, so in reality you probably want a sanity-check to run each time the tools JSON has been rebuilt, telling you what changed. Seems a good thing to have in your CI/CD.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Distributed Tracing with Node JS]]></title>
            <link>https://edspencer.net//2020/10/13/distributed-tracing-with-node-js</link>
            <guid>distributed-tracing-with-node-js</guid>
            <pubDate>Tue, 13 Oct 2020 07:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The microservice architecture pattern solves many of the problems inherent with monolithic applications. But microservices also bring challenges of their own, one of which is figuring out what went wrong when something breaks. There are at least 3 related challenges here:</p>
<ul>
<li>Log collection</li>
<li>Metric collection</li>
<li>Distributed tracing</li>
</ul>
<p>Log and metric collection is fairly straightforward (we'll cover these in a separate post), but only gets you so far.</p>
<p>Let's say your 20 microservice application starts behaving badly - you start getting timeouts on a particular API and want to find out why. The first place you look may be your centralized metrics service. This will likely confirm to you that you have a problem, as hopefully you have one or more metrics that are now showing out-of-band numbers.</p>
<p>But what if the issue only affects part of your user population, or worse, a single (but important) customer? In these cases your metrics - assuming you have the right ones in the first place - probably won't tell you much.</p>
<p>In cases like these, where you have minimal or no guidance from your configured metrics, you start trying to figure out where the problem may be. You know your system architecture, and you're pretty sure you've narrowed the issue down to three or four of your services.</p>
<p>So what's next? Well, you've got your centrally aggregated service logs, right? So you open up three or four windows and try to find an example of a request that fails, and trace it through to the other 2-3 services in the mix. Of course, if your problem only manifests in production then you'll be sifting through a large number of logs.</p>
<p>How good are you logs anyway? You're in prod, so you've probably disabled debug logs, but even if you hadn't, logs usually only get you so far. After some digging, you might be able to narrow things down to a function or two, but you're likely not logging all the information you need to proceed from there. Time to start sifting through code...</p>
<p>But maybe there's a better way.</p>
<h2>Enter Distributed Tracing</h2>
<p>Distributed Tracing is a method of tracking a request as it traverses multiple services. Let's say you have a simple e-commerce app, which looks a little like this (simplified for clarity):</p>
<p><img src="/images/posts/open-tracing-example.png" alt=""></p>
<p>Now, your user has made an order and wants to track the order's status. In order for this to happen the user makes a request that hits your API Gateway, which needs to authenticate the request and then send it on to your Orders service. This fetches Order details, then consults your Shipping service to discover shipping status, which in turn calls an external API belonging to your shipping partner.</p>
<p>There are quite a few things that can go wrong here. Your Auth service could be down, your Orders service could be unable to reach its database, your Shipping service could be unable to access the external API, and so on. All you know, though, is that your customer is complaining that they can't access their Order details and they're getting aggravated.</p>
<p>We can solve this by tracing a request as it traverses your architecture, with each step surfacing details about what is going on and what (if anything) went wrong. We can then use the <a href="http://localhost:16686/">Jaeger UI</a> to visualize the trace as it happened, allowing us to debug problems as well as identify bottlenecks.</p>
<h2>An example distributed application</h2>
<p>To demonstrate how this works I've created a <a href="https://github.com/edspencer/tracing-example">distributed tracing example app on Github</a>. The repo is pretty basic, containing a packages directory that contains 4 extremely simple apps: gateway, auth, orders and shipping, corresponding to 4 of the services in our service architecture diagram.</p>
<p>The easiest way to play with this yourself is to simply clone the repo and start the services using docker-compose:</p>
<pre><code>git clone git@github.com:edspencer/tracing-example.git
cd tracing-example
docker-compose up
</code></pre>
<p>This will spin up 5 docker containers - one for each of our 4 services plus Jaeger. Now go to http://localhost:5000/orders/12345 and hit refresh a few times. I've set the services up to sometimes work and sometimes cause errors - there's a 20% chance that the auth app will return an error and a 30% chance that the simulated call to the external shipping service API will fail.</p>
<p>After refreshing http://localhost:5000/orders/12345 a few times, open up the Jaeger UI at http://localhost:16686/search and you'll see something like this:</p>
<p><img src="/images/posts/jaeger-ui-overview.png" alt=""></p>
<p>http://localhost:5000/orders/12345 serves up the Gateway service, which is a pretty simple <a href="https://github.com/edspencer/tracing-example/blob/master/packages/gateway/service.js">one-file express app</a> that will call the Auth service on every request, then make calls to the Orders service. The Orders service in turn calls the Shipping service, which makes a simulated call to the external shipping API.</p>
<p>Clicking into one of the traces will show you something like this:</p>
<p><img src="/images/posts/jaeger-ui-trace.png" alt=""></p>
<p>This view shows you the the request took 44ms to complete, and has a nice breakdown of where that time was spent. The services are color coded automatically so you can see at a glance how the 44ms was distributed across them. In this case we can see that there was an error in the shipping service. Clicking into the row with the error yields additional information useful for debugging:</p>
<p><img src="/images/posts/jaeger-ui-trace-error.png" alt=""></p>
<p>The contents of this row are highly customizable. It's easy to tag the request with whatever information you like. So let's see how this works.</p>
<h2>The Code</h2>
<p>Let's look at the <a href="https://github.com/edspencer/tracing-example/blob/master/packages/gateway/service.js">Gateway service</a>. First we set up the Jaeger integration:</p>
<pre><code>const express = require('express')
const superagent = require('superagent')
const opentracing = require('opentracing')
const {initTracer} = require('jaeger-client')

const port = process.env.PORT || 80
const authHost = process.env.AUTH_HOST || "auth"
const ordersHost = process.env.ORDERS_HOST || "orders"
const app = express()

//set up our tracer
const config = {
  serviceName: 'gateway',
  reporter: {
    logSpans: true,
    collectorEndpoint: 'http://jaeger:14268/api/traces',
  },
  sampler: {
    type: 'const',
    param: 1
  }
};

const options = {
  tags: {
    'gateway.version': '1.0.0'
  }
};

const tracer = initTracer(config, options);
</code></pre>
<p>The most interesting stuff here is where we declare our config. Here we're telling the Jaeger client tracer to post its traces to http://jaeger:14268/api/traces (this is set up in our <a href="https://github.com/edspencer/tracing-example/blob/master/docker-compose.yml">docker-compose file</a>), and to sample all requests - as specified in the sampler config. In production, you won't want to sample every request - one in a thousand is probably enough - so you can switch to type: 'probabilistic' and param: 0.001 to achieve this.</p>
<p>Now that we have our tracer, let's tell Express to instrument each request that it serves:</p>
<pre><code>//create a root span for every request
app.use((req, res, next) => {
  req.rootSpan = tracer.startSpan(req.originalUrl)
  tracer.inject(req.rootSpan, "http_headers", req.headers)

  res.on("finish", () => {
    req.rootSpan.finish()
  })

  next()
})
</code></pre>
<p>Here we're setting up our outer span and giving it a title matching the request url. We encounter 3 of the 4 simple concepts we need to understand:</p>
<ul>
<li>startSpan - creates a new "span" in our distributed trace; this corresponds to one of the rows we see in the Jaeger UI. This span is given a unique span ID and may have a parent span ID</li>
<li>inject - adds the span ID somewhere else - usually into HTTP headers for a downstream request - we'll see more of this in a moment</li>
<li>finishing the span - we hook into Express' "finish" event on the response to make sure we call .finish() on the span. This is what sends it to Jaeger.</li>
</ul>
<p>Now let's see how we call the Auth service, passing along the span ID:</p>
<pre><code>//use the auth service to see if the request is authenticated
const checkAuth = async (req, res, next) => {
  const span = tracer.startSpan("check auth", {
    childOf: tracer.extract(opentracing.FORMAT_HTTP_HEADERS, req.headers)
  })

  try {
    const headers = {}
    tracer.inject(span, "http_headers", headers)
    const res = await superagent.get(http://${authHost}/auth).set(headers)

    if (res &#x26;&#x26; res.body.valid) { 
      span.setTag(opentracing.Tags.HTTP_STATUS_CODE, 200) 
      next() 
    } else { 
      span.setTag(opentracing.Tags.HTTP_STATUS_CODE, 401) 
      res.status(401).send("Unauthorized") 
    }
  } catch(e) { 
    res.status(503).send("Auth Service gave an error") 
  }

  span.finish()
}
</code></pre>
<p>There are 2 important things happening here:</p>
<ul>
<li>We create a new span representing the "check auth" operation, and set it to be the childOf the parent span we created previously</li>
<li>When we send the superagent request to the Auth service, we inject the new child span into the HTTP request headers</li>
</ul>
<p>We're also showing how to add tags to a span via setTag. In this case we're appending the HTTP status code that we return to the client.</p>
<p>Let's examine the final piece of the Gateway service - the actual proxying to the Orders service:</p>
<pre><code>//proxy to the Orders service to return Order details
app.all('/orders/:orderId', checkAuth, async (req, res) => {
  const span = tracer.startSpan("get order details", {
    childOf: tracer.extract(opentracing.FORMAT_HTTP_HEADERS, req.headers)
  })
  try {
    const headers = {}
    tracer.inject(span, "http_headers", headers)
    const order = await superagent.get(http://${ordersHost}/order/${req.params.orderId}).set(headers)
    if (order &#x26;&#x26; order.body) {
      span.finish()
      res.json(order.body)
    } else { 
      span.setTag(opentracing.Tags.HTTP_STATUS_CODE, 200) 
      span.finish()
      res.status(500).send("Could not fetch order")
    }
  } catch(e) {
    res.status(503).send("Error contacting Orders service") 
  }
})

app.listen(port, () => console.log(`API Gateway app listening on port ${port}`))
</code></pre>
<p>This looks pretty similar to what we just did for the Auth service - we're creating a new span that represents the call to the Orders service, setting its parent to our outer span, and injecting it into the superagent call we make to Orders. Pretty simple stuff.</p>
<p>Finally, let's look at the other side of this - how to pick up the trace in another service - in this case <a href="https://github.com/edspencer/tracing-example/blob/master/packages/auth/service.js">the Auth service</a>:</p>
<pre><code>//simulate our auth service being flaky with a 20% chance of 500 internal server error
app.get('/auth', (req, res) => {
  const parentSpan = tracer.extract(opentracing.FORMAT_HTTP_HEADERS, req.headers)
  const span = tracer.startSpan("checking user", {
    childOf: parentSpan, tags: {
      [opentracing.Tags.COMPONENT]: "database" 
    }
  })

  if (Math.random() > 0.2) {
    span.finish()
    res.json({valid: true, userId: 123})
  } else {
    span.setTag(opentracing.Tags.ERROR, true) 
    span.finish() 
    res.status(500).send("Internal Auth Service error") 
  }
})
</code></pre>
<p>Here we see the 4th and final concept involved in distributed tracing:</p>
<ul>
<li>extract - pulls the trace ID from the upstream service from the incoming HTTP headers</li>
</ul>
<p>This is how the trace is able to traverse our services - in service A we create a span and inject it into calls to service B. Service B picks it up and creates a new span with the extracted span as its parent. We can then pass this span ID on to service C.</p>
<p>Jaeger is even nice enough to automatically create a system architecture diagram for you:</p>
<p><img src="/images/posts/jaeger-ui-system-arch-diagram.png" alt=""></p>
<h2>Conclusion</h2>
<p>Distributed tracing is immensely powerful when it comes to understanding why distributed systems behave the way they do. There is a lot more to distributed tracing than we covered above, but at its core it really comes down to those 4 key concepts: starting spans, finishing them, injecting them into downstream requests and extracting them from the upstream.</p>
<p>One nice attribute of open tracing standards is that they work across technologies. In this example we saw how to hook up 4 Node JS microservices with it, but there's nothing special about Node JS here - this stuff is well supported in other languages like Go and can be added pretty much anywhere - it's just basic UDP and (usually) HTTP.</p>
<p>For further reading I recommend you check out the <a href="https://www.jaegertracing.io/docs/1.20/">Jaeger intro docs</a>, as well as the <a href="https://www.jaegertracing.io/docs/1.20/architecture/">architecture</a>. The <a href="https://github.com/jaegertracing/jaeger-client-node">Node JS Jaeger client repo</a> is a good place to poke around, and has links to more resources. Actual example code for Node JS was a little hard to come by, which is why I wrote this post. I hope it helps you in your microservice applications.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/open-tracing-example.png?w=830" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[A New Stack for 2016: Getting Started with React, ES6 and Webpack]]></title>
            <link>https://edspencer.net//2016/3/20/a-new-stack-for-2016-getting-started-with-react-es6-and-webpack</link>
            <guid>a-new-stack-for-2016-getting-started-with-react-es6-and-webpack</guid>
            <pubDate>Sun, 20 Mar 2016 05:16:20 GMT</pubDate>
            <content:encoded><![CDATA[<p>A lot has changed in the last few years when it comes to implementing applications using JavaScript. Node JS has revolutionized how many of us create backend apps, React has become a widely-used standard for creating the frontend, and ES6 has come along and completely transformed JavaScript itself, largely for the better.</p>
<p>All of this brings new capabilities and opportunities, but also new challenges when it comes to figuring out what's worth paying attention to, and how to learn it. Today we'll look at how to set up my personal take on a sensible stack in this new world, starting from scratch and building it up as we go. We'll focus on getting to the point where everything is set up and ready for you to create the app.</p>
<p>The stack we'll be setting up today is as follows:</p>
<ul>
<li>React - to power the frontend</li>
<li>Babel - allows us to use ES6 syntax in our app</li>
<li>Webpack - builds our application files and dependencies into a single build</li>
</ul>
<p>Although we won't be setting up a Node JS server in this article, we'll use npm to put everything else in place, so adding a Node JS server using Express or any other backend framework is trivial. We're also going to omit setting up a testing infrastructure in this post - this will be the subject of the next article.</p>
<p>If you want to get straight in without reading all the verbiage, you can <a href="https://github.com/edspencer/react-es6-webpack-starter">clone this github repo</a> that contains all of the files we're about to create.</p>
<h2>Let's go</h2>
<p>The only prerequisite here is that your system has Node JS already installed. If that isn't the case, go install it now from http://nodejs.org. Once you have Node, we'll start by creating a new directory for our project and setting up NPM:</p>
<pre><code class="language-bash">mkdir myproject
npm init
</code></pre>
<p>The npm init command takes you through a short series of prompts asking for information about your new project - author name, description, etc. Most of this doesn't really matter at this stage - you can easily change it later. Once that's done you'll find a new file called package.json in your project directory.</p>
<p>Before we take a look at this file, we already know that we need to bring in some dependencies, so we'll do that now with the following terminal commands:</p>
<pre><code class="language-bash">npm install react --save
npm install react-dom --save
npm install webpack --save-dev
</code></pre>
<p>Note that for the react dependency we use --save, whereas for webpack we use --save-dev. This indicates that react is required when running our app in production, whereas webpack is only needed while developing (as once webpack has created your production build, its role is finished). Opening our package.json file now yields this:</p>
<pre><code class="language-json">{
   "name": "myproject",
   "version": "1.0.0",
   "description": "",
   "main": "index.js",
   "scripts": {
       "test": "echo \"Error: no test specified\" &#x26;&#x26; exit 1"
   },
   "author": "",
   "license": "ISC",
   "dependencies": {
     "react": "^0.14.7",
     "react-dom": "^0.14.7"
   },
   "devDependencies": {
     "webpack": "^1.12.14"
   }
 }
</code></pre>
<p>This is pretty straightforward. Note the separate dependencies and devDependencies objects in line with our --save vs --save-dev above. Depending on when you created your app the version numbers for the dependencies will be different, but the overall shape should be the same.</p>
<p>We're not done installing npm packages yet, but before we get started with React and ES6 we're going to get set up with Webpack.</p>
<h2>Setting up Webpack</h2>
<p>We'll be using Webpack to turn our many application files into a single file that can be loaded into the browser. As it stands, though, we don't have any application files at all. So let's start by creating those:</p>
<pre><code class="language-bash">mkdir src
touch src/index.js
touch src/App.js
</code></pre>
<p>Now we have a src directory with two empty files. Into App.js, we'll place the following trivial component rendering code:</p>
<pre><code class="language-js">var App = function() {
  return "&#x3C;h1>Woop&#x3C;/h1>";
};

module.exports = App;

</code></pre>
<p>All we're doing here is returning an HTML string when you call the App function. Once we bring React into the picture we'll change the approach a little, but this is good enough for now. Into our src/index.js, we'll use:</p>
<pre><code class="language-js">var app = require('./App');
document.write(app());

</code></pre>
<p>So we're simply importing our App, running it and then writing the resulting HTML string into the DOM. Webpack will be responsible for figuring out how to combine index.js and App.js and building them into a single file. In order to use Webpack, we'll create a new file called webpack.config.js (in the root directory of our project) with the following contents:</p>
<pre><code class="language-js">var path = require('path');
var webpack = require('webpack');

module.exports = {
  output: {
    filename: 'bundle.js'
  },
  entry: [
    './src/index.js'
  ]
};

</code></pre>
<p>This really couldn't be much simpler - it's just saying take the entry point (our src/index.js file) as input, and save the output into a file called bundle.js. Webpack takes those entry file inputs, figures out all of the require('...') statements and fetches all of the dependencies as required, outputting our bundle.js file.</p>
<p>To run Webpack, we simply use the <code>webpack</code> command in our terminal, which will do something like this:</p>
<p><img src="/images/posts/webpack.png" alt=""></p>
<p>As we can see, we now have a 1.75kb file called bundle.js that we can serve up in our project. That's a little heavier than our index.js and App.js files combined, because there is a little Webpack plumbing that gets included into the file too.</p>
<p>Now finally we'll create a very simple index.html file that loads our bundle.js and renders our app:</p>
<pre><code>&#x3C;html>
  &#x3C;head>
    &#x3C;meta charset="utf-8">
  &#x3C;/head>
  &#x3C;body>
    &#x3C;div id="main">&#x3C;/div>
    &#x3C;script type="text/javascript" src="bundle.js" charset="utf-8">&#x3C;/script>
  &#x3C;/body>
 &#x3C;/html>

</code></pre>
<p>Can't get much simpler than that. We don't have a web server set up yet, but we don't actually need one. As we have no backend we can just load the index.html file directly into the browser, either by dragging it in from your OS's file explorer program, or entering the address manually. For me, I can enter file:///Users/ed/Code/myproject/index.html into my browser's address bar, and be greeted with the following:</p>
<p><img src="/images/posts/woop.png" alt=""></p>
<p>Great! That's our component being rendered and output into the DOM as desired. Now we're ready to move onto using React and ES6.</p>
<h2>React and ES6</h2>
<p>React can be used either with or without ES6. Because this is the future, we desire to use the capabilities of ES6, but we can't do that directly because most browsers currently don't support it. This is where babel comes in.</p>
<p>Babel (which you'll often hear pronounced "babble" instead of the traditional "baybel") a transpiler, which takes one version of the JavaScript language and translates it into another. In our case, it will be translating the ES6 version of JavaScript into an earlier version that is guaranteed to run in browsers. We'll start by adding a few new npm package dependencies:</p>
<pre><code class="language-bash">npm install babel-core --save-dev
npm install babel-loader --save-dev
npm install babel-preset-es2015 --save-dev
npm install babel-preset-react --save-dev
npm install babel-plugin-transform-runtime --save-dev

npm install babel-polyfill --save
npm install babel-runtime --save
</code></pre>
<p>This is quite a substantial number of new dependencies. Because babel can convert between many different flavors of JS, once we've specified the babel-core and babel-loader packages, we also need to specify babel-preset-es2015 to enable ES6 support, and babel-preset-react to enable React's JSX syntax. We also bring in a polyfill that makes available new APIs like Object.assign that babel would not usually bring to the browser as it requires some manipulation of the browser APIs, which is something one has to opt in to.</p>
<p>Once we have these all installed, however, we're ready to go. The first thing we'll need to do is update our webpack.config.js file to enable babel support:</p>
<pre><code>var path = require('path');
var webpack = require('webpack');

module.exports = {
  module: {
    loaders: [
      {
        loader: "babel-loader",
        // Skip any files outside of your project's `src` directory
        include: [
          path.resolve(__dirname, "src"),
        ],
        // Only run `.js` and `.jsx` files through Babel
        test: /\.jsx?$/,
        // Options to configure babel with
        query: {
          plugins: ['transform-runtime'],
          presets: ['es2015', 'react'],
        }
      }
    ]
  },
  output: {
    filename: 'bundle.js'
  },
  entry: [
    './src/index.js'
  ]
};

</code></pre>
<p>Hopefully the above is clear enough - it's the same as last time, with the exception of the new module object, which contains a loader configuration that we've configured to convert any file that ends in .js or .jsx in our src directory into browser-executable JavaScript.</p>
<p>Next we'll update our App.js to look like this:</p>
<pre><code class="language-js">import React, {Component} from 'react';

class App extends Component {
  render() {
    return (&#x3C;h1>This is React!&#x3C;/h1>);
  }
}
export default App;

</code></pre>
<p>Cool - new syntax! We've switched from require('') to import, though this does essentially the same thing. We've also switched from <code>module.exports = </code> to <code>export default </code>, which is again doing the same thing (though we can export multiple things this way).</p>
<p>We're also using the ES6 class syntax, in this case creating a class called App that extends React's Component class. It only implements a single method - render - which returns a very similar HTML string to our earlier component, but this time using inline JSX syntax instead of just returning a string.</p>
<p>Now all that remains is to update our index.js file to use the new Component:</p>
<pre><code class="language-js">import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(&#x3C;App />, document.getElementById("main"));
</code></pre>
<p>Again we're using the import syntax to our advantage here, and this time we're using ReactDOM.render instead of document.write to place the rendered HTML into the DOM. Once we run the <code>webpack</code> command again and refresh our browser window, we'll see a screen like this:</p>
<p><img src="/images/posts/react-heading.png" alt=""></p>
<h2>Next Steps</h2>
<p>We'll round out by doing a few small things to improve our workflow. First off, it's annoying to have to switch back to the terminal to run <code>webpack</code> every time we change any code, so let's update our webpack.config.js with a few new options:</p>
<pre><code class="language-js">module.exports = {
  //these remain unchanged
  module: {...},
  output: {...},
  entry: [...],

  //these are new
  watch: true,
  colors: true,
  progress: true
};
</code></pre>
<p>Now we just run <code>webpack</code> once and it'll stay running, rebuilding whenever we save changes to our source files. This is generally much faster - on my 2 year old MacBook Air it takes about 5 seconds to run <code>webpack</code> a single time, but when using watch mode each successive build is on the order of 100ms. Usually this means that I can save my change in my text editor, and by the time I've switched to the browser the new bundle.js has already been created so I can immediately refresh to see the results of my changes.</p>
<p>The last thing we'll do is add a second React component to be consumed by the first. This one we'll call src/Paragraph.js, and it contains the following:</p>
<pre><code class="language-js">import React, {Component} from 'react';

export default class Paragraph extends Component {
  render() {
    return (&#x3C;p>{this.props.text}&#x3C;/p>);
  }
}
</code></pre>
<p>This is almost identical to our App, with a couple of small tweaks. First, notice that we've moved the <code>export default</code> inline with the class declaration to save on space, and then secondly this time we're using <code>{this.props}</code> to access a configured property of the Paragraph component. Now, to use the new component we'll update App.js to look like the following:</p>
<pre><code class="language-js">import React, {Component} from 'react';
import Paragraph from './Paragraph';

export default class App extends Component {
  render() {
    return (
      &#x3C;div className="my-app">
        &#x3C;h1>This is React!!!&#x3C;/h1>
        &#x3C;Paragraph text="First Paragraph" />
        &#x3C;Paragraph text="Second Paragraph" />
      &#x3C;/div>
    );
  }
}
</code></pre>
<p>Again a few small changes here. First, note that we're now importing the Paragraph component and then using it twice in our render() function - each time with a different <code>text</code> property, which is what is read by <code>{this.props.text}</code> in the Paragraph component itself. Finally, React requires that we return a single root element for each rendered Component, so we wrap our &#x3C;h1> and &#x3C;Paragraph> tags into an enclosing &#x3C;div></p>
<p>By the time you hit save on those changes, webpack should already have built a new bundle.js for you, so head back to your browser, hit refresh and you'll see this:</p>
<p><img src="/images/posts/react-with-paragraphs.png" alt=""></p>
<p>That's about as far as we'll take things today. The purpose of this article was to get you to a point where you can start building a React application, instead of figuring out how to set up all the prerequisite plumbing; hopefully it's clear enough how to continue from here.</p>
<p>You can find a <a href="https://github.com/edspencer/react-es6-webpack-starter">starter repository containing all of the above over on GitHub</a>. Feel free to clone it as the starting point for your own project, or just look through it to see how things fit together.</p>
<p>In the next article, we'll look at how to add some unit testing to our project so that we can make sure our Components are behaving as they should. Until then, happy Reacting!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/webpack.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Jasmine and Jenkins Continuous Integration]]></title>
            <link>https://edspencer.net//2013/7/28/jasmine-and-jenkins-continuous-integration</link>
            <guid>jasmine-and-jenkins-continuous-integration</guid>
            <pubDate>Sun, 28 Jul 2013 10:25:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>I use <a href="http://pivotal.github.io/jasmine/">Jasmine</a> as my JavaScript unit/behavior testing framework of choice because it's elegant and has a good community ecosystem around it. I recently wrote up how to get <a href="http://edspencer.net/2013/06/15/autotesting-javascript-with-jasmine-and-guard/">Jasmine-based autotesting</a> set up with Guard, which is great for development time testing, but what about continuous integration?</p>
<p>Well, it turns out that it's pretty difficult to get Jasmine integrated with <a href="http://jenkins-ci.org/">Jenkins</a>. This is not because of an inherent problem with either of those two, it's just that no-one got around to writing an open source integration layer until now.</p>
<p>The main problem is that Jasmine tests usually expect to run in a browser, but Jenkins needs results to be exposed in .xml files. Clearly we need some bridge here to take the headless browser output and dump it into correctly formatted .xml files. Specifically, these xml files need to follow the JUnit XML file format for Jenkins to be able to process them. Enter <a href="https://github.com/netzpirat/guard-jasmine">guard-jasmine</a>.</p>
<h3>guard-jasmine</h3>
<p>In my previous article on <a href="http://edspencer.net/2013/06/15/autotesting-javascript-with-jasmine-and-guard/">getting Jasmine and Guard set up</a>, I was using the jasmine-headless-webkit and guard-jasmine-headless-webkit gems to provide the glue. Since then I've replaced those 2 gems with a single gem - <a href="https://github.com/netzpirat/guard-jasmine">guard-jasmine</a>, written by Michael Kessler, the Guard master himself. This simplifies our dependencies a little, but doesn't buy us the .xml file functionality we need.</p>
<p>For that, I had to hack on the gem itself (which involved writing coffeescript for the first time, which was not a horrible experience). The guard-jasmine gem now exposes 3 additional configurations:</p>
<ul>
<li><strong>junit</strong> - set to true to save output to xml files (false by default)</li>
<li><strong>junit_consolidate</strong> - rolls nested describes up into their parent describe blocks (true by default)</li>
<li><strong>junit_save_path</strong> - optional path to save the xml files to</li>
</ul>
<p>The JUnit Xml reporter itself borrows heavily from <a href="https://github.com/larrymyers">larrymyers</a>' excellent <a href="https://github.com/larrymyers/jasmine-reporters">jasmine-reporters</a> project. Aside from a few changes to integrate it into guard-jasmine it's the same code, so all credit goes to to Larry and Michael.</p>
<h3>Sample usage:</h3>
<p>In your <a href="https://github.com/guard/guard">Guardfile</a>:</p>
<pre><code class="language-ruby">guard :jasmine, :junit => true, :junit_save_path => 'reports' do
  watch(%r{^spec/javascripts/.+$}) { 'spec/javascripts' }
  watch(%r{^spec/javascripts/fixtures/.+$}) { 'spec/javascripts' }
  watch(%r{^app/assets/javascripts/(.+?)\.(js\.coffee|js|coffee)(?:\.\w+)*$}) { 'spec/javascripts' }
end
</code></pre>
<p>This will just run the full set of Jasmine tests inside your spec/javascripts directory whenever any test, source file or asset like CSS files change. This is generally the configuration I use because the tests execute so fast I can afford to have them all run every time.</p>
<p>In the example above we set the :junit_save_path to 'reports', which means it will save all of the .xml files into the reports directory. It is going to output 1 .xml file for each Jasmine spec file that is run. In each case the name of the .xml file created is based on the name of the top-level <code>describe</code> block in your spec file.</p>
<p>To test that everything's working, just run <code>bundle exec guard</code> as you normally would, and check to see that your <code>reports</code> folder now contains a bunch of .xml files. If it does, everything went well.</p>
<h3>Jenkins Settings</h3>
<p>Once we've got the .xml files outputting correctly, we just need to tell Jenkins where to look. In your Jenkins project configuration screen, click the Add Build Step button and add a "Publish JUnit test result report" step. Enter 'reports/*.xml' as the <code>Test report XMLs</code> field.</p>
<p>If you've already got Jenkins running your test script then you're all done. Next time a build is triggered the script should run the tests and export the .xml files. If you don't already have Jenkins set up to run your tests, but you did already set up Guard as per my <a href="http://edspencer.net/2013/06/15/autotesting-javascript-with-jasmine-and-guard/">previous article</a>, you can actually use the same command to run the tests on Jenkins.</p>
<p>After a little experimentation, people tend to come up with a build command like this:</p>
<pre><code class="language-bash">bash -c ' bundle install --quiet \
&#x26;&#x26; bundle exec guard '
</code></pre>
<p>If you're using rvm and need to guarantee a particular version you may need to prepend an <code>rvm install</code> command before <code>bundle install</code> is called. This should just run guard, which will dump the files out as expected for Jenkins to pick up.</p>
<p>To clean up, we'll just add a second post-build action, this time choosing the "Execute a set of scripts" option and entering the following:</p>
<pre><code class="language-bash">kill -9 `cat guard.pid`
</code></pre>
<p>This just kills the Guard process, which ordinarily stays running to power your autotest capabilities. Once you run a new build you should see a chart automatically appear on your Jenkins project page telling you full details of how many tests failed over time and in the current build.</p>
<h3>Getting it</h3>
<p><strong>Update</strong>: The Pull Request is now merged into the main guard-jasmine repo so you can just use <code>gem 'guard-jasmine'</code> in your Gemfile</p>
<p>This is hot off the presses but I wanted to write it up while it's still fresh in my mind. At the time of writing the <a href="https://github.com/netzpirat/guard-jasmine/pull/141">pull request</a> is still outstanding on the guard-jasmine repository, so to use the new options you'll need to temporarily use <a href="https://github.com/edspencer/guard-jasmine">my guard-jasmine fork</a>. In your Gemfile:</p>
<pre><code class="language-ruby">gem 'guard-jasmine'
</code></pre>
<p>Once the PR is merged and a new version issued you should switch back to the official release channel. It's working well for me but it's fresh code so may contains bugs - YMMV. Hopefully this helps save some folks a little pain!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sencha Con 2013 Wrapup]]></title>
            <link>https://edspencer.net//2013/7/21/sencha-con-2013-wrapup</link>
            <guid>sencha-con-2013-wrapup</guid>
            <pubDate>Sun, 21 Jul 2013 05:02:10 GMT</pubDate>
            <content:encoded><![CDATA[<p>So another great Sencha Con is over, and I'm left to reflect on everything that went on over the last few days. This time was easily the biggest and best Sencha Con that I've been to, with 800 people in attendance and a very high bar set by the speakers. The organization was excellent, the location fun (even if the bars don't open until 5pm...), and the enthusiasm palpable.</p>
<p>I've made a few posts over the last few days so won't repeat the content here - if you want to see what else happened check these out too:</p>
<ul>
<li><a href="http://edspencer.net/2013/07/18/sencha-con-2013-day-1/">Day 1 Wrapup</a></li>
<li><a href="http://edspencer.net/2013/07/19/sencha-con-2013-fastbook/">The Making of Fastbook</a></li>
<li><a href="http://edspencer.net/2013/07/19/sencha-con-2013-ext-js-performance-tips/">Ext JS Performance Tips</a></li>
</ul>
<p>What I will do though is repeat my invitation to take a look at what we're doing with JavaScript at C3 Energy. I wrote up a <a href="http://edspencer.net/2013/07/19/sencha-con-attendees-i-need-you/">quick post about it</a> yesterday and would love to hear from you - whether you're at Sencha Con or not.</p>
<p>Now on to some general thoughts.</p>
<h3>Content</h3>
<p>There was a large range in the technical difficulty of the content, with perhaps a slightly stronger skew up the difficulty chain compared to previous events. This is a good thing, though there's probably still room for more advanced content. Having been there before though, I know how hard it is to pitch that right so that everyone enjoys and gets value of out it.</p>
<p>The biggest challenge for me was the sheer number of tracks - at any one time there would be seven talks happening simultaneously, two or three of which I'd really want to watch. Personally I'd really love it if the hackathon was dropped in favor of a third day of sessions, with a shift down to 4-5 tracks. I'm sure there's a cost implication to that, but it's worth thinking about.</p>
<h3>Videos</h3>
<p>There were cameras set up in at least the main hall on the first day, but I didn't see any on day 2. I did overhear that the video streams were being recorded directly from what was being shown on the projectors, with the audio recorded separately. If that's true I'd guess it would make editing a bit easier so maybe that'll means a quick release.</p>
<p>Naturally, take this with a pinch of salt until the official announcement comes out. In the meantime, there's at least one video available so far:</p>
<p><a href="https://www.facebook.com/video/embed?video_id=10201574572036378"><img src="/images/posts/grgur.png" alt=""></a></p>
<h3>Fun Things</h3>
<p>The community pavilion was a great idea, and served as the perfect space for attendees with hang out away from the other rascals running around the hotel. Coffee and snacks were available whenever I needed them, and there was plenty of seating to chill out in.</p>
<p>I missed out on the visit to the theme park, which I hear was by far the most fun part of the event. Having a theme park kick out everyone but Sencha Con attendees while serving copious amounts of alcohol seemed to go down very well with the attendees!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/grgur.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sencha Con Attendees: I Need You]]></title>
            <link>https://edspencer.net//2013/7/19/sencha-con-attendees-i-need-you</link>
            <guid>sencha-con-attendees-i-need-you</guid>
            <pubDate>Fri, 19 Jul 2013 04:08:30 GMT</pubDate>
            <content:encoded><![CDATA[<p>Love working with Sencha frameworks? Want to come work with me on the next generation? I moved on to <a href="http://c3energy.com">C3 Energy</a> about a year ago, where we are busily building the operating system for the largest machine ever conceived by humans - the Smart Grid.</p>
<p>The Smart Grid is an amazing concept that's being rolled out <strong>right now</strong>. C3 Energy is the only company in existence that addresses the full stack of Smart Grid architecture - from generation through transmission and end-user consumption.</p>
<p>But what's that got to do with JavaScript? Well, my team gets to work on building the UI that powers everything that happens on the smart grid. We have some unique requirements that have led us to write our own beautiful little framework, optimized for end-user performance and developer productivity. Naturally, this leaves me feeling like this:</p>
<p><a href="/images/posts/framework-meme.png"><img src="/images/posts/framework-meme.png" alt=""></a></p>
<p>We're a small (70 person) company of exceptionally talented people. We have a staggeringly successful collection of people both <a href="http://c3energy.com/about-board-of-directors">on the board</a> and as <a href="http://c3energy.com/about-executive-team">the executive team</a>.</p>
<p>We'd like to attract more people like us, and the Sencha community is the perfect place to look - especially given how much the framework has been inspired by what I helped create at Sencha.</p>
<p>If you're intrigued but don't know much about this space, I can't recommend this video enough. This is a presentation our CEO Tom Siebel gave a few months back, introducing why the company exists, which problems it's solving, and why we're doing what we're doing. If you can watch this without getting excited, this probably isn't for you :)</p>
<p><a href="http://fora.tv/2013/02/06/keynote_speaker_tom_siebel"><img src="/images/posts/siebs.png" alt=""></a></p>
<p>You'll get to work alongside people like this every day at C3. It's really an incomparable feeling, and I'd love to introduce you to it. If you're interested in finding out more in a low pressure way, drop me a comment or a tweet (<a href="http://twitter.com/edspencer">@edspencer</a>) or come grab me so I can buy you a beer.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/framework-meme.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sencha Con 2013: Ext JS Performance tips]]></title>
            <link>https://edspencer.net//2013/7/19/sencha-con-2013-ext-js-performance-tips</link>
            <guid>sencha-con-2013-ext-js-performance-tips</guid>
            <pubDate>Fri, 19 Jul 2013 02:08:07 GMT</pubDate>
            <content:encoded><![CDATA[<p>Just as with <a href="http://edspencer.net/2013/07/19/sencha-con-2013-fastbook/">Jacky's session</a>, I didn't plan on making a separate post about this, but again the content was so good and I ended up taking so many notes that it also warrants its own space. To save myself from early carpal tunnel syndrome I'm going to leave this one in more of a bullet point format.</p>
<p><a href="/images/posts/nige.jpg"><img src="/images/posts/nige.jpg" alt=""></a></p>
<p>Ext JS has been getting more flexible with each release. You can do many more things with it these days than you used to be able to, but there has been a performance cost associated with that. In many cases this performance degradation is down to the way the framework is being used, as opposed to a fundamental problem with the framework itself.</p>
<p>There's a whole bunch of things that you can do to dramatically speed up the performance of an app you're not happy with, and Nige "Animal" White took us through them this morning. Here's what I was able to write down in time:</p>
<h4>Slow things</h4>
<p>Nige identified three of the top causes of sluggish apps, which we'll go through one by one:</p>
<ul>
<li>Network latency</li>
<li>JS execution</li>
<li>Layout activity</li>
</ul>
<h4>Network latency:</h4>
<ul>
<li>Bad ux - got to stare at blank screen for a while</li>
<li>Use Sencha Command to build the app - single file, minimized</li>
<li>4810ms vs 352ms = dynamic loading vs built</li>
</ul>
<h4>JavaScript execution:</h4>
<ul>
<li>Avoid slow JS engines (he says with a wry smile)</li>
<li>Optimize repeated code - for loops should be tight, cache variables outside</li>
<li>Ideally, don't do any processing at render time</li>
<li>Minimize function calls</li>
<li>Lazily instantiate items</li>
<li>Use the PageAnalyzer (in the Ext JS SDK examples folder) to benchmark your applications</li>
<li>Start Chrome with --enable-benchmarking to get much more accurate timing information out of the browser</li>
</ul>
<h4>Layouts</h4>
<p>Suspend store events when adding/removing many records. Otherwise we're going to get a full Ext JS layout pass for each modification</p>
<pre><code> grid.store.suspendEvents();
 //do lots of updating
 grid.store.resumeEvents();
 grid.view.refresh()

</code></pre>
<p>Ditto on trees (they're the same as grids)
Coalesce multiple layouts. If you're adding/removing a bunch of Components in a single go, do it like this:</p>
<pre><code> Ext.suspendLayouts();
 //do a bunch of UI updates
 Ext.resumeLayouts(true);

</code></pre>
<p>Container#add accepts an array of items, which is faster than iterating over that array yourself and calling .add for each one. Avoid layout constraints where possible - in box layouts, align: 'stretchmax' is slow because it has to do multiple layout runs. Avoid minHeight, maxHeight, minWidth, maxWidth if possible</p>
<h4>At startup:</h4>
<ul>
<li>Embed initialization data inside the HTML if possible - avoids AJAX requests</li>
<li>Configure the entire layout in one shot using that data</li>
<li>Do not make multiple Ajax requests, and build the layout in response</li>
</ul>
<h4>Use the 'idle' event</h4>
<ul>
<li>Similar to the <a href="http://edspencer.net/2013/07/19/sencha-con-2013-fastbook/">AnimationQueue</a></li>
<li>Ext.globalEvents.on('idle', myFunction) - called once a big layout/repaint run has finished</li>
<li>Using the idle listener sometimes preferable to setTimeout(myFunction, 1), because it's synchronous in the same repaint cycle. The setTimeout approach means the repaint happens, then your code is called. If your code itself requires a repaint, that means you'll have 2 repaints in setTimeout vs 1 in on.('idle')</li>
</ul>
<h4>Reduce layout depth</h4>
<p>Big problem - overnesting. People very often do this with grids:</p>
<pre><code>{
    xtype: 'tabpanel',
    items: [
        {
            title: 'Results',
            items: {
                xtype: 'grid'
            }
        }
    ]
}
</code></pre>
<p>Better:</p>
<pre><code>{
    xtype: 'tabpanel',
    items: {
        title: 'Results',
        xtype: 'grid'
    }
}
</code></pre>
<p>This is important because redundant components still cost CPU and memory. Everything is a Component now - panel headers, icons, etc etc. Can be constructing more Components than you realize. Much more flexible, but easy to abuse</p>
<h4>Lazy Instantiation</h4>
<p>New plugin at https://gist.github.com/ExtAnimal/c93148f5194f2a232464</p>
<pre><code>{
    xtype: 'tabpanel',
    ptype: 'lazyitems',
    items: {
        title: 'Results',
        xtype: 'grid'
    }
}
</code></pre>
<h4>Overall impact</h4>
<p>On a real life large example contributed by a Sencha customer:</p>
<p>Bad practices: 5187ms (IE8)
Good practices: 1813ms (IE8)
1300ms vs 550ms on Chrome (same example)</p>
<p>Colossal impact on the Ext.suspendLayout example - 4700ms vs 100ms on Chrome</p>
<h4>Summary</h4>
<p>This is definitely a talk you'll want to watch when they go online. It was absolutely brimming with content and the advice comes straight from the horse's mouth. Nige did a great job presenting, and reminded us that performance is a shared responsibility - the framework is getting faster as time goes by, but we the developers need to do our share too to make sure it stays fast.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/nige.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Sencha Con 2013: Fastbook]]></title>
            <link>https://edspencer.net//2013/7/19/sencha-con-2013-fastbook</link>
            <guid>sencha-con-2013-fastbook</guid>
            <pubDate>Fri, 19 Jul 2013 01:05:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>I didn't plan on writing a post purely on Fastbook, but Jacky's presentation just now was so good I felt it needed one. If you haven't seen <a href="http://www.sencha.com/blog/the-making-of-fastbook-an-html5-love-story">Fastbook</a> yet, it is Sencha's answer to the (over reported) comments by Zuckerburg that using HTML5 for Facebook's mobile app was a mistake.</p>
<p><a href="/images/posts/jacky-presenting.jpg"><img src="/images/posts/jacky-presenting.jpg" alt=""></a></p>
<p>After those comments there was a lot of debate around whether HTML5 is ready for the big time. Plenty of opinions were thrown around, but not all based on evidence. Jacky was curious about why Facebook's old app was so slow, and wondered if he could use the same technologies to achieve a much better result. To say he was successful would be a spectacular understatement - Fastbook absolutely flies.</p>
<p>Performance can be hard to describe in words, so Sencha released this video that demonstrates the HTML5 Fastbook app against the new native Facebook apps. As you can see, not only is the HTML5 version at least as fast and fluid as the native versions, in several cases it's actually significantly better (especially on Android).</p>
<p><a href="http://vimeo.com/55486684"><img src="/images/posts/fastbook-thumb.png" alt="Fastbook"></a></p>
<h3>Challenges</h3>
<p>The biggest challenge here is dynamically loading and scrolling large quantities of data while presenting a 60fps experience to the user. 60fps means you have just 16.7ms per frame to do everything, which is a hugely tall order on a CPU and memory constrained mobile device.</p>
<p>The way to achieve this is to treat the page as an app rather than a traditional web page. This means we need to be a lot more proactive in managing how and when things are rendered - something that traditionally has been in the domain of the browser's own rendering and layout engines. Thankfully, the framework will do all of this for you.</p>
<p>As an example, Jacky loaded up Gmail's web app and showed what happens when you scroll a long way down your inbox. The more you scroll, the more divs are added to the document (one new div per message). Each div contains a bunch of child elements too, so we're adding maybe a dozen or so nodes to our DOM tree per message.</p>
<p>The problem with this is that as the DOM tree gets larger and larger, everything slows down. You could see the inspector showing slower and slower layout recalculations, making the app sluggish.</p>
<p>The solution is to recycle DOM nodes once they're no longer visible. In this way, a list that seems to have infinite content could contain only say 10 elements - just enough to fill the screen. Once you scroll down the list, DOM nodes that scrolled off the top are detached, updated with new data and placed at the bottom of the list. Simple. Ingenius. Beautiful.</p>
<h3>Prioritization</h3>
<p>There's usually a lot more going on in an app than just animating a scrolling view though. There's data to load via AJAX, images to load, compositing, processing, and whatever else your app needs to do. And then there are touch events, which need to feel perfectly responsive at all times, even while all of this is going on.</p>
<p>To make this sane and manageable, we have a new class called AnimationQueue. All of the jobs I just mentioned above - handling touch events, animation, network requests and so on - are dispatched through the AnimationQueue with a given priority. Touch event handling has the top priority, followed by animation, followed by everything else.</p>
<p>AnimationQueue does as much as it can in that 16.7ms window, then breaks execution again to allow the browser to reflow/repaint/whatever else it needs to do. What this means is that while scrolling down a large list, it's likely that our CPU/GPU is being taxed so much that we don't have any time to load images or other low priority jobs.</p>
<p>This is a Good Thing, because if we're scrolling through a large list there's a good chance we are going to skip right over those images anyway. In the end they're loaded as soon as the AnimationQueue has some spare time, which is normally when your scrolling of the list has slowed down or stopped.</p>
<h3>Sandboxing</h3>
<p>The final, and most complex technique Jacky discussed was Sandboxing. The larger your application gets, the larger the DOM tree. Even if you are using best practices, there's an expense to simply having so many components on the same page. The bottleneck here is in the browser itself - looks like we need another hack.</p>
<p>To get around this, we can dynamically create iframes that contain parts of our DOM tree. This way our main page DOM tree can remain small but we can still have a huge application. This not only speeds up browser repaint and reflow, it also improves compositing performance, DOM querying and more.</p>
<p>This all happens under the covers and Jacky's aiming on including Ext.Sandbox in Sencha Touch 2.3 so that all apps can take advantage of this huge improvement. He cautioned (rightly) that it'll only make 2.3 if it's up to his high standards though, so watch this space.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/jacky-presenting.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Sencha Con 2013 Day 1]]></title>
            <link>https://edspencer.net//2013/7/18/sencha-con-2013-day-1</link>
            <guid>sencha-con-2013-day-1</guid>
            <pubDate>Thu, 18 Jul 2013 08:02:42 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sencha Con 2013 kicked off today, with some stunning improvements demoed across the product set. I'm attending as an audience member for the first time so thought I'd share how things look from the cheap seats.</p>
<h3>Keynote</h3>
<p>The keynote was very well put together, with none of the AV issues that plagued us last year (maybe they seemed worse from behind the curtain!). It started off with a welcome from Paul Kopacki, followed by some insights into the current status of developers in the world of business (apparently we're kingmakers - who knew!). One of Blackberry's evangelists came up and made a pretty good pitch for giving them a second look (the free hardware probably helped a little...)</p>
<p>The meat, though, was in the second half of the presentation. We were treated to a succession of great new features across Ext JS, Sencha Touch and Sencha Architect, which I'll go into in a little more detail below.</p>
<p>But it was Abe Elias and Jacky Nguyen who stole the show in the end. Unleashing a visionary new product, Sencha Space, they demonstrated a brand new way to enable businesses to elegantly solve the problem of BYOD (Bring Your Own Device).</p>
<p>Nobody wants to be given a mobile phone by their IT department when they've got a brand new iPhone in their pocket. But those IT guys have good reason for doing this - consumer browsers are currently inherently insecure. Sencha Space solves this problem by providing a single app that employees can install, log in to and gain access to all of the apps needed to be productive in the company.</p>
<p>I could write a lot more about it but the 2 minute video below can surely do a better job:</p>
<p><strong>Update: looks like this video got taken down at some point</strong></p>
<h3>Ext JS upgrades</h3>
<p>The keynote lasted most of the morning, but in the afternoon Don Griffin came back on stage to tell us more about what's coming soon in Ext JS. Don heads up Ext JS these days, and is one of the most intelligent and experienced people I've had the joy of working with. I'm pretty sure he gained the largest amount of spontaneous applause of the day during the Ext JS talk, which is no surprise given the awesome stuff he showed us.</p>
<p>I forget which order things were revealed in, but these things stood out for me:</p>
<ul>
<li>Touch Support - while this may seem anathema to the thinking behind Ext JS, it's an undeniable fact that people try to use Ext JS applications on tablets. Whether they should or not is a different question, but in this next release it will be officially supported by the framework. Momentum scrolling, pinch to zoom and dragdrop resizing are all supported at your fingertips.</li>
<li>Grid Gadgets - quite likely the coolest new feature, Gadgets allow you to render any Component into each cell in a Grid, in an extremely CPU and memory efficient manner. Seeing a live grid updating with rich charts and other widgets at high frequency was a fantastic experience</li>
<li>Border Layout - allows your users to rearrange the border layouts used in your apps with drag and drop. Easy to switch between accordion layout, box layout or tabs</li>
<li>A shedload more. The enforced pub crawl has temporarily relieved me of a full memory. So impressed with everything that was demonstrated today.</li>
</ul>
<h3>Sencha Touch upgrades</h3>
<p>Jacky came up and delivered a presentation on what's coming up in Sencha Touch, using his idiosyncratic and inimitable style. Some of the things that stood out for me:</p>
<ul>
<li>Touch gets a grid. It performs really well and looks great. Good for (sparing) use on tablet apps</li>
<li>XML configs. Not sure how I feel about this yet, but ST 2.3 will allow for views to be declared in XML, which is transformed into the normal JSON format under the covers. You end up writing few lines of code, but the overall file size probably doesn't change too much. With a decent editor the syntax highlighting definitely makes the View code easier to read though</li>
<li>ViewModel. Just as we have Ext.data.Model for encapsulating data models, we now have ViewModel for encapsulating a view model, which includes things like state. Leads to a much improved API for updating Views in response to other changes</li>
<li>Theming. 2 additional themes were added, and the others have all been refactored to make theming even easier</li>
</ul>
<p>Again there's a lot more here and I couldn't possibly do it all justice in a blog post. It's geniunely thrilling to see these young frameworks mature into stellar products that are being used by literally millions of developers. Very exciting.</p>
<h3>Architect upgrades</h3>
<p>Architect has come a really long way since its inception a couple of years ago. The new features introduced today looked like some of the largest steps forward the product has ever taken. I'm finally getting close to actually thinking about using it in real life (I'm a glutten for editing code in Sublime Text). Some standout features:</p>
<ul>
<li>New template apps to get you up and running with a new app in seconds</li>
<li>Integration with Appurify, which allows you to test your Architect apps on real devices hosted by their service</li>
<li>Allows you to install third party extensions into Architect, and have them seamlessly integrated into your project</li>
</ul>
<h3>Day 1 Summary</h3>
<p>Although I worked with these people for years, somehow I'm still surprised when I see every single developer giving world class presentations. I don't know how I was able to leave Sencha a year ago, but every time I interact with Abe, Don, Jacky, Tommy, Jamie, Rob, Nige, and all of the other rockstars at that place I'm reminded what a great and unique time that was. Really looking forward to what tomorrow brings!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Autotesting JavaScript with Jasmine and Guard]]></title>
            <link>https://edspencer.net//2013/6/15/autotesting-javascript-with-jasmine-and-guard</link>
            <guid>autotesting-javascript-with-jasmine-and-guard</guid>
            <pubDate>Sat, 15 Jun 2013 02:03:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the things I really loved about Rails in the early days was that it introduced me to the concept of autotest - a script that would watch your file system for changes and then automatically execute your unit tests as soon as you change any file.</p>
<p>Because the unit test suite typically executes quickly, you'd tend to have your test results back within a second or two of hitting save, allowing you to remain in the editor the entire time and only break out the browser for deeper debugging - usually the command line output and OS notifications (growl at the time) would be enough to set you straight.</p>
<p>This was a fantastic way to work, and I wanted to get there again with JavaScript. Turns out it's pretty easy to do this. Because I've used a lot of ruby I'm most comfortable using its ecosystem to achieve this, and as it happens there's a great way to do this already.</p>
<h2>Enter Guard</h2>
<p>Guard is a simple ruby gem that scans your file system for changes and runs the code of your choice whenever a file you care about is saved. It has a great ecosystem around it which makes automating filesystem-based triggers both simple and powerful. Let's start by making sure we have all the gems we need:</p>
<pre><code class="language-bash">gem install jasmine jasmine-headless-webkit guard-jasmine-headless-webkit guard \
 guard-livereload terminal-notifier-guard --no-rdoc --no-ri
</code></pre>
<p>This just installs a few gems that we're going to use for our tests. First we grab the excellent <a href="http://pivotal.github.io/jasmine/">Jasmine JavaScript BDD test framework</a> via its gem - you can use the framework of your just but I find Jasmine both pleasant to deal with and it generally Just Works. Next we're going to add the 'jasmine-headless-webkit' gem and its guard twin, which use phantomjs to run your tests on the command line, without needing a browser window.</p>
<p>Next up we grab guard-livereload, which enables Guard to act as a livereload server, automatically running your full suite in the browser each time your save a file. This might sound redundant - our tests are already going to be executed in the headless webkit environment, so why bother running them in the browser too? Well, the browser Jasmine runner tends to give a lot more information when something goes wrong - stack traces and most importantly a live debugger.</p>
<p>Finally we add the terminal-notifier-guard gem, which just allows guard to give us a notification each time the tests finish executing. Now we've got our dependencies in line it's time to set up our environment. Thankfully both jasmine and guard provide simple scripts to get started:</p>
<pre><code class="language-bash">jasmine init
guard init
</code></pre>
<p>And we're ready to go! Let's test out our setup by running <code>guard</code>:</p>
<pre><code class="language-bash">guard
</code></pre>
<p>What you should see at this point is something like this:</p>
<p><img src="/images/posts/guard-screenshot.png" alt=""></p>
<p>We see guard starting up, telling us it's going to use TerminalNotifier to give us an OS notification every time the tests finish running, and that it's going to use JasmineHeadlessWebkit to run the tests without a browser. You'll see that 5 tests were run in about 5ms, and you should have seen an OS notification flash up telling you the same thing. This is great for working on a laptop where you don't have the screen real estate to keep a terminal window visible at all times.</p>
<p>What about those 5 tests? They're just examples that were generated by <code>jasmine init</code>. You can find them inside the spec/javascripts directory and by default there's just 1 - PlayerSpec.js.</p>
<p>Now try editing that file and hitting save - nothing happens. The reason for this is that the Guardfile generated by <code>guard init</code> isn't quite compatible out of the box with the Jasmine folder structure. Thankfully this is trivial to fix - we just need to edit the Guardfile.</p>
<p>If you open up the Guardfile in your editor you'll see it has about 30 lines of configuration. A large amount of the file is comments and optional configs, which you can delete if you like. Guard is expecting your spec files to have the format 'my_spec.js' - note the '_spec' at the end.</p>
<p>To get it working the easiest way is to edit the 'spec_location' variable (on line 7 - just remove the '_spec'), and do the same to the last line of the <code>guard 'jasmine-headless-webkit' do</code> block. You should end up with something like this:</p>
<pre><code>
spec_location = "spec/javascripts/%s"

guard 'jasmine-headless-webkit' do
watch(%r{^app/views/.*\.jst$})
watch(%r{^public/javascripts/(.*)\.js$}) { |m| newest_js_file(spec_location % m[1]) }
watch(%r{^app/assets/javascripts/(.*)\.(js|coffee)$}) { |m| newest_js_file(spec_location % m[1]) }
watch(%r{^spec/javascripts/(.*)\..*}) { |m| newest_js_file(spec_location % m[1]) }
end

</code></pre>
<p>Once you save your Guardfile, there's no need to restart guard, it'll notice the change to the Guardfile and automatically restart itself. Now when you save PlayerSpec.js again you'll see the terminal immediately run your tests and show your the notification that all is well (assuming your tests still pass!).</p>
<p>So what are those 4 lines inside the <code>guard 'jasmine-headless-webkit' do</code> block? As you've probably guessed they're just the set of directories that guard should watch. Whenever any of the files matched by the patterns on those 4 lines change, guard will run its jasmine-headless-webkit command, which is what runs your tests. These are just the defaults, so if your JS files are not found inside those folders jus update it to point to the right place.</p>
<h2>Livereload</h2>
<p>The final part of the stack that I use is livereload. Livereload consists of two things - a browser plugin (available for Chrome, Firefox and others), and a server, which have actually already set up with Guard. First you'll need to install the <a href="http://livereload.com/">livereload browser plugin</a>, which is extremely simple.</p>
<p>Because the livereload server is already running inside guard, all we need to do is give our browser a place to load the tests from. Unfortunately the only way I've found to do this is to open up a second terminal tab and in the same directory run:</p>
<pre><code class="language-bash">rake jasmine
</code></pre>
<p>This sets up a lightweight web server that runs on http://localhost:8888. If you go to that page in your browser now you should see something like this:</p>
<p><img src="/images/posts/livereload-screenshot.png" alt=""></p>
<p>Just hit the livereload button in your browser (once you've installed the plugin), edit your file again and you'll see the browser automatically refreshes itself and runs your tests. This step is optional but I find it extremely useful to get a notification telling me my tests have started failing, then be able to immediately tab into the browser environment to get a full stack trace and debugging environment.</p>
<p>That just about wraps up getting autotest up and running. Next time you come back to your code just run <code>guard</code> and <code>rake jasmine</code> and you'll get right back to your new autotesting setup. And if you have a way to have guard serve the browser without requiring the second tab window please share in the comments!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/guard-screenshot.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[On Leaving Sencha]]></title>
            <link>https://edspencer.net//2012/6/27/on-leaving-sencha</link>
            <guid>on-leaving-sencha</guid>
            <pubDate>Wed, 27 Jun 2012 11:16:55 GMT</pubDate>
            <content:encoded><![CDATA[<p>As some of you may know, I left Sencha last week to move to <a href="http://www.c3energy.com/">another startup</a> just up the road in San Mateo. Leaving the company was a hugely difficult thing to do for lots of reasons, some obvious, some less so. I'd like to share a few thoughts on my time there and look forward a little to the future.</p>
<p>I first came across Sencha's products when I saw an early preview of Ext JS 2 way back in 2007. I thought it was amazing stuff, and I started using it all over the place despite being a Ruby guy at the time. As time went by and I got deeper into the language and the framework, it became clear that JavaScript was the future, even though most people at the time still thought that was a little crazy.</p>
<p>I didn't really intend to join the company. I was having fun writing components and exploring the framework from the outside already, but a chance meeting in San Francisco with the team changed all that. What I found was a small but immensely talented group of people who loved what they did - writing awesome frameworks all day. Underqualified though I felt, being invited into that group was an honor I couldn't really refuse.</p>
<h2>Early Days</h2>
<p>When I started back in late 2009, Ext JS 3.1 was just being wrapped up for release so I leapt straight into creating 3.2. Having only ever consumed the framework before, making the leap to creating brand new components was quite a challenge. Thankfully Sencha can count many veterans in its ranks, and Jamie in particular demonstrated his saintly patience in bringing me up to speed.</p>
<p>Ext JS 3.2 saw the addition of animated DataView transitions, composite fields and a few Toolbar plugins. It also required some upgrades to Store, which was a horrifying enough experience that I'd spend a few weeks rewriting the entire data package for Sencha Touch and Ext JS 4. 3.2 also saw the first of my <a href="http://www.sencha.com/blog/announcing-ext-js-3-2-beta-multisort-transitions-and-composite-fields">allegedly bombastic blog posts</a> (I'm just enthusiastic...)!</p>
<p>All this time we were a very small group working out of a picturesque little office on University Avenue in Palo Alto. During that first year we grew to maybe 25 people and all fit happily into the one big open plan room, descending en masse upon one of the many restaurants along the strip or bringing food back to eat in the sunny courtyard outside the office.</p>
<p><img src="/images/posts/paoffice.png" alt="The original Palo Alto office"></p>
<p>I think of that time as the happiest part of my Sencha experience. Somehow I'd found myself in the heart of Silicon Valley surrounded by unbelievably talented people, creating groundbreaking products - some of which we were even allowed to give away for free! We worked like crazy, often well into the early hours of the morning, but it was a lot of fun and I think we created a lot we can be proud of in that time.</p>
<h2>Creating Sencha Touch, Learning how to Conference</h2>
<p>Not long after Ext JS 3.2 went final, and in parallel with Ext JS 3.3, we started creating Sencha Touch. The initial work was all from Tommy and Dave, before I got a chance to jump in and start writing the new data package. Over time most of the team got a chance to put their name on Touch as we raced to create the world's first HTML5 mobile app framework. Creating a new product from scratch like that was an awesome experience, and the final product was pretty good (though nowhere near as good as we'd get it with 2.0).</p>
<p>SenchaCon 2010 was scheduled for mid-November and we'd decided we wanted to make a big splash by releasing Sencha Touch - for free. Naturally, this meant a lot of work in a very short period of time in the months running up to the conference. I have vivid memories of a particular evening (read: 3am) in the office just before an imminent release. That can be stressful enough at the best of times but this particular evening our fire alarm would not stop going off. I don't know whether it was the people, the project or the pressure, but what should have been a dreadful night was a really fun experience. And I think it paid off - we shipped on time at the conference, but only just.</p>
<p>This would be a pattern we'd repeat more than once - working night and day to create both products and presentations that have an immovable deadline. Once more it amazes me how talented my friends at Sencha really are: how many developers do you know who can write great code and deliver world-class conference presentations? That all came from a lot of hard work but it's one more reason why it was so hard to leave that group of people behind.</p>
<p><img src="/images/posts/edasleep.png" alt="Ed Asleep"></p>
<h2>Later Days</h2>
<p>Later on, our time was dominated first by Ext JS 4, then by Sencha Touch 2. I was able to make a couple of contributions to Ext JS 4 - chiefly the new data package plus an evolution of the MVC architecture that debuted in Sencha Touch 1. I probably spent as much time writing <a href="http://docs.sencha.com/ext-js/4-1/">documentation</a> as I did writing code though, which is a pattern I'd later <a href="http://docs.sencha.com/touch/2-0/">repeat on Sencha Touch 2</a>. For whatever reason there's a misalignment in my brain that makes me pretty passionate about docs, so if you're reading the guides and class docs from those projects and none of it makes sense, well, sorry! (but you should see <a href="http://extjs.cachefly.net/ext-3.4.0/docs/">how it was before...</a>)</p>
<p>By this time we'd outgrown our little office in Palo Alto and moved to a much bigger space in Redwood City. With 5x the floor space at our disposal the company started growing like crazy, easily expanding by a factor of 10 during the time I was there. That transition was harder than I expected - at 10 people it was like a large family, at 100 it was definitely a Company. I think a lot of that is down to Sencha's success, but it still caught me off guard having never been through that before.</p>
<p>I think the thing I'm proudest of during my time at Sencha was the release of Sencha Touch 2. This was the first release where we got (almost) everything right - the quality was high, the performance was great, and we finally cracked MVC. We even launched with relatively good docs and examples from day one, though I've learned by now that you can never have enough of that stuff.</p>
<h2>People/Future</h2>
<p>As well as getting to work with so many talented people inside the company, I've also been lucky enough to meet a huge number of people from the Sencha community. If anything you guys seem even more passionate about our stuff than we are. Until SenchaCon I could honestly say I'd never been mobbed but for those few days a year you make us all feel like rockstars. We may not say it at the time but I know everyone involves gets a huge high from those interactions, so thanks.</p>
<p>While I'm at a new company now I expect to stay active in the Sencha community, I'm far too attached to what we created together to leave that behind any time soon. I'll stay active on the forums and maybe even blog once a while - if you want to get in touch feel free to reach out here, on <a href="http://twitter.com/edspencer">twitter</a> or <a href="http://www.linkedin.com/pub/ed-spencer/2/877/809">linkedin</a>, or if you're near Palo Alto maybe I'll buy you a beer.</p>
<p>Sencha's best days are ahead of it and they have a great team there to deliver on the mission. I remain a big fan of the company, its people, its products and especially its community and can't wait to see what happens next.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/paoffice.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Anatomy of a Sencha Touch 2 App]]></title>
            <link>https://edspencer.net//2012/3/19/anatomy-of-a-sencha-touch-2-app</link>
            <guid>anatomy-of-a-sencha-touch-2-app</guid>
            <pubDate>Mon, 19 Mar 2012 04:10:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>At its simplest, a Sencha Touch 2 application is just a small collection of text files - html, css and javascript. But applications often grow over time so to keep things organized and maintainable we have a set of simple conventions around how to structure and manage your application's code.</p>
<p>A little while back we introduced a technology called Sencha Command. Command got a big overhaul for 2.0 and today it can generate all of the files your application needs for you. To get Sencha Command you'll need to install the SDK Tools and then open up your terminal. To run the app generator you'll need to make sure you've got a copy of the Sencha Touch 2 SDK, cd into it in your terminal and run the app generate command:</p>
<pre><code class="language-bash">sencha generate app MyApp ../MyApp
</code></pre>
<p>This creates an application called MyApp with all of the files and folders you'll need to get started generated for you. You end up with a folder structure that looks like this:</p>
<p><img src="/images/posts/st2-dir-overview1.png" alt="Sencha Touch 2 Directory Overview"></p>
<p>This looks like a fair number of files and folders because I've expanded the <em>app</em> folder in the image above but really there are only 4 files and 3 folders at the top level. Let's look at the files first:</p>
<ul>
<li><strong>index.html</strong>: simplest HTML file ever, just includes the app JS and CSS, plus a loading spinner</li>
<li><strong>app.js</strong>: this is the heart of your app, sets up app name, dependencies and a launch function</li>
<li><strong>app.json</strong>: used by the microloader to cache your app files in localStorage so it boots up faster</li>
<li><strong>packager.json</strong>: configuration file used to package your app for native app stores</li>
</ul>
<p>To begin with you'll only really need to edit app.js - the others come in useful later on. Now let's take a look at the folders:</p>
<ul>
<li><strong>app</strong>: contains all of your application's source files - models, views, controllers etc</li>
<li><strong>resources</strong>: contains the images and CSS used by your app, including the source SASS files</li>
<li><strong>sdk</strong>: contains the important parts of the Touch SDK, including Sencha Command</li>
</ul>
<h2>The app folder</h2>
<p>You'll spend 90%+ of your time inside the app folder, so let's drill down and take a look at what's inside that. We've got 5 subfolders, all of which are empty except one - the <em>view</em> folder. This just contains a template view file that renders a tab panel when you first boot the app up. Let's look at each:</p>
<ul>
<li><strong>controller</strong>: will contain all of your Controller files (<a href="http://docs.sencha.com/touch/2-0/#!/guide/controllers">learn about Controllers</a>)</li>
<li><strong>model</strong>: will contain all of your Model files (<a href="http://docs.sencha.com/touch/2-0/#!/guide/models">learn about Models</a>)</li>
<li><strong>profile</strong>: will contain all of your Profile classes (<a href="http://docs.sencha.com/touch/2-0/#!/guide/profiles">learn about Device Profiles</a>)</li>
<li><strong>store</strong>: will contain all of your Store classes (<a href="http://docs.sencha.com/touch/2-0/#!/guide/stores">learn about Stores</a>)</li>
<li><strong>view</strong>: will contain all of your View classes (<a href="http://docs.sencha.com/touch/2-0/#!/guide/views">learn about creating Views</a>)
Easy stuff. There's a bunch of documentation on what each of those things are at the <a href="http://docs.sencha.com/touch/2-0/">Touch 2 docs site</a>, plus of course the <a href="http://vimeo.com/37974749">Getting Started video</a> with awesome narration by some British guy.</li>
</ul>
<h2>The resources folder</h2>
<p>Moving on, let's take a look at the <em>resources</em> folder:</p>
<p><img src="/images/posts/st2-dir-resources.png" alt="Sencha Touch 2 resources folder"></p>
<p>Five folders this time - in turn:</p>
<ul>
<li><strong>icons</strong>: the set of icons used when your app is added to the home screen. We create some nice default ones for you</li>
<li><strong>loading</strong>: the loading/startup screen images to use when your app's on a home screen or natively packaged</li>
<li><strong>images</strong>: this is where you should put any app images that are not icons or loading images</li>
<li><strong>sass</strong>: the source SASS files for your app. This is the place to alter the theming for your app, remove any CSS you're not using and add your own styles</li>
<li><strong>css</strong>: the <em>compiled</em> SASS files - these are the CSS files your app will use in production and are automatically minified for you</li>
</ul>
<p>There are quite a few icon and loading images needed to cover all of the different sizes and resolutions of the devices that Sencha Touch 2 supports. We've included all of the different formats with the conventional file names as a guide - you can just replace the contents of <em>resources/icons</em> and <em>resources/loading</em> with your own images.</p>
<h2>The sdk folder</h2>
<p>Finally there's the SDK directory, which contains the SDK's source code and all of the dependencies used by Sencha Command. This includes Node.js, Phantom JS and others so it can start to add up. Of course, none of this goes into your production builds, which we keep as tiny and fast-loading as possible, but if you're not going to use the SDK Tools (bad move, but your call!) you can remove the <em>sdk/command</em> directory to keep things leaner.</p>
<p>By vendoring all third-party dependencies like Node.js into your application directory we can be confident that there are no system-specific dependencies required, so you can zip up your app, send it to a friend and so long as she has the SDK Tools installed, everything should just work.</p>
<p>Hopefully that lays out the large-scale structure of what goes where and why - feel free to ask questions!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/st2-dir-overview1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[What do you want from Sencha Touch 2.1?]]></title>
            <link>https://edspencer.net//2012/3/14/what-do-you-want-from-sencha-touch-2-1</link>
            <guid>what-do-you-want-from-sencha-touch-2-1</guid>
            <pubDate>Wed, 14 Mar 2012 16:07:45 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Disclaimers</strong>: this is the most unofficial, non-Sencha-backed poll of all time.  There's no guarantee we'll ever do any of it, yada yada.</p>
<p>Touch 2.0 went GA last week to easily the best product launch reception I've seen. It was great and the feedback's wonderful but honeymoons are boring - I want to know what's wrong with it :)</p>
<p>So, what do you want to see in Sencha Touch 2.1? I asked on Twitter just now and got a bunch of responses so here are some ideas. Even if what you want is on this list already drop a reply in the comments so I know more than one person cares about it:</p>
<p><img src="/images/posts/touch-2-1-requests.png" alt="Some replies to my Twitter poll"></p>
<p>We do of course have a few ideas up our sleeves too, but why spoil the surprise?</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/touch-2-1-requests.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sencha Touch 2 GA Released!]]></title>
            <link>https://edspencer.net//2012/3/6/sencha-touch-2-ga-released</link>
            <guid>sencha-touch-2-ga-released</guid>
            <pubDate>Tue, 06 Mar 2012 05:51:19 GMT</pubDate>
            <content:encoded><![CDATA[<p>The last few months have flown by faster than almost any before them. The first Sencha Touch 2 release <a href="http://www.sencha.com/blog/sencha-touch-2-developer-preview/">went out in October</a> as a Developer Preview, coinciding with SenchaCon 2011, sparking a huge wave of interest from all over the HTML5 community. Today marks the GA release of Sencha Touch 2.0.0, and we couldn't be happier with how far we've come.</p>
<p><a href="http://www.sencha.com/blog/announcing-sencha-touch-2">See the announcement on sencha.com</a></p>
<p>It was only 18 months ago that we released the first version of Sencha Touch 1. It ushered in a brave new world, bringing tried and true approaches from the desktop together with the exciting new capabilities of the mobile web. But it was, to many of us, very much a version 1 product. ST2 is as big a step up from ST1 as ST1 was from everything that went before it.</p>
<h2>Similar themes, great execution</h2>
<p>For me, there are three core themes that go into any game-changing software release: Performance, Stability and Ease of Use. These themes come up again and again, especially for products at the bleeding edge of what's possible. With the mobile web, each year can bring game changing developments - that's one of the reasons developing Sencha Touch is so exciting.</p>
<p>Touch 2 really nails all of those themes. <strong>Performance</strong> was definitely the top priority of the three, and I think we've been able to permanently alter expectations of what web apps can do with the improvements we've made. Not only do apps feel much faster when you use them, today's announcement on the incredible new fast startup performance is game changing in itself. It's amazing how fast ST2 apps start up now, it really changes how they're used when they consistently boot in a couple of seconds.</p>
<p><strong>Stability</strong> is not sexy. It's not a particularly great marketing ploy but it's really important. Having a weekly release cycle and a huge developer community has really helped enable a fast turnaround on finding, fixing and verifying bugs. Lately new bug reports have been trickling in so slowly we almost wish there were more of them. Almost.</p>
<p>Oddly, <strong>documentation</strong> is something that's really close to my heart. Back when I joined Sencha, improving the Ext JS docs was a prime concern and I think we took it up several notches between Ext JS 3 and 4. But that's nothing compared to what we've done between Sencha Touch 1 and 2. As well as using the awesome new documentation app from Ext JS, we've added a ludicrous amount of new content including 35 brand new guides (up from zero) and way more examples than ever before.</p>
<p>We've also been creating new screencasts, augmenting the awesome material Drew Neil creates. For the Touch 2 GA release I created this 30 minute getting started video that covers everything from generating an app all the way through to packaging it for native app stores. This is the sort of material I always wished I had when I was first learning Ext JS:</p>
<p><a href="http://www.vimeo.com/37974749"><img src="/images/posts/getting-started-with-sencha-touch-thumb.png" alt="Getting Started with Sencha Touch 2"></a></p>
<p>These take a lot of time to produce so if you like this sort of thing make sure you drop a comment here or on the sencha.com blog so I know to create more. I try to record them in a single take so you know there's no magic going on behind the scenes - I think it shows it's all real but it involves a lot of takes :)</p>
<h2>Step One</h2>
<p>All of the hard work to date has been to ship 2.0.0 - the first stable 2.x release. 2.0 itself is a step change over what went before it but there are already some incredible things lined up for the next few releases. Over the next couple of months we'll continue to polish, optimize and act on the wonderful feedback you guys are providing, but first I think we've earned a break!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/getting-started-with-sencha-touch-thumb.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Building a data-driven image carousel with Sencha Touch 2]]></title>
            <link>https://edspencer.net//2012/2/11/building-a-data-driven-image-carousel-with-sencha-touch-2</link>
            <guid>building-a-data-driven-image-carousel-with-sencha-touch-2</guid>
            <pubDate>Sat, 11 Feb 2012 01:04:02 GMT</pubDate>
            <content:encoded><![CDATA[<p>This evening I embarked on a little stellar voyage that I'd like to share with you all. Most people with great taste love astronomy and Sencha Touch 2, so why not combine them in a fun evening's web app building?</p>
<p>NASA has been running a small site called <a href="http://apod.nasa.gov/apod/astropix.html">APOD</a> (Astronomy Picture Of the Day) for a long time now, as you can probably tell by the <a href="http://apod.nasa.gov/apod/astropix.html">awesome web design</a> of that page. Despite its 1998-era styling, this site incorporates some pretty stunning images of the universe and is begging for a mobile app interpretation.</p>
<p>We're not going to go crazy, in fact this whole thing only took about an hour to create, but hopefully it's a useful look at how to put something like this together. In this case, we're just going to write a quick app that pulls down the last 20 pictures and shows them in a carousel with an optional title.</p>
<p>Here's what it looks like live. You'll need a webkit browser (Chrome or Safari) to see this, alternatively load up <a href="http://code.edspencer.net/apod">http://code.edspencer.net/apod</a> on a phone or tablet device:</p>
<p>The full source code for <a href="https://github.com/edspencer/APOD">the app is up on github</a>, and we'll go through it bit by bit below.</p>
<h2>The App</h2>
<p>Our app consists of 5 files:</p>
<ul>
<li><strong>index.html</strong>, which includes our JavaScript files and a little CSS</li>
<li><strong>app.js</strong>, which boots our application up</li>
<li><strong>app/model/Picture.js</strong>, which represents a single APOD picture</li>
<li><strong>app/view/Picture.js</strong>, which shows a picture on the page</li>
<li><strong>app/store/Pictures.js</strong>, which fetches the pictures from the APOD RSS feed</li>
</ul>
<p>The whole thing is up on github and you can <a href="http://code.edspencer.net/apod">see a live demo at http://code.edspencer.net/apod</a>. To see what it's doing tap that link on your phone or tablet, and to really feel it add it to your homescreen to get rid of that browser chrome.</p>
<h2>The Code</h2>
<p>Most of the action happens in app.js, which for your enjoyment is more documentation than code. Here's the gist of it:</p>
<pre><code class="language-javascript">/*
 * This app uses a Carousel and a JSON-P proxy so make sure they're loaded first
 */
Ext.require([
    'Ext.carousel.Carousel',
    'Ext.data.proxy.JsonP'
]);

/**
 * Our app is pretty simple - it just grabs the latest images from NASA's Astronomy Picture Of the Day 
 * (http://apod.nasa.gov/apod/astropix.html) and displays them in a Carousel. This file drives most of
 * the application, but there's also:
 * 
 * * A Store - app/store/Pictures.js - that fetches the data from the APOD RSS feed
 * * A Model - app/model/Picture.js - that represents a single image from the feed
 * * A View - app/view/Picture.js - that displays each image
 * 
 * Our application's launch function is called automatically when everything is loaded.
 */
Ext.application({
    name: 'apod',
    
    models: ['Picture'],
    stores: ['Pictures'],
    views: ['Picture'],
    
    launch: function() {
        var titleVisible = false,
            info, carousel;
        
        /**
         * The main carousel that drives our app. We're just telling it to use the Pictures store and
         * to update the info bar whenever a new image is swiped to
         */
        carousel = Ext.create('Ext.Carousel', {
            store: 'Pictures',
            direction: 'horizontal',
            
            listeners: {
                activeitemchange: function(carousel, item) {
                    info.setHtml(item.getPicture().get('title'));
                }
            }
        });
        
        /**
         * This is just a reusable Component that we pin to the top of the page. This is hidden by default
         * and appears when the user taps on the screen. The activeitemchange listener above updates the 
         * content of this Component whenever a new image is swiped to
         */
        info = Ext.create('Ext.Component', {
            cls: 'apod-title',
            top: 0,
            left: 0,
            right: 0
        });
        
        //add both of our views to the Viewport so they're rendered and visible
        Ext.Viewport.add(carousel);
        Ext.Viewport.add(info);
        
        /**
         * The Pictures store (see app/store/Pictures.js) is set to not load automatically, so we load it 
         * manually now. This loads data from the APOD RSS feed and calls our callback function once it's
         * loaded.
         * 
         * All we do here is iterate over all of the data, creating an apodimage Component for each item. 
         * Then we just add those items to the Carousel and set the first item active.
         */
        Ext.getStore('Pictures').load(function(pictures) {
            var items = [];
            
            Ext.each(pictures, function(picture) {
                if (!picture.get('image')) {
                    return;
                }
                
                items.push({
                    xtype: 'apodimage',
                    picture: picture
                });
            });
            
            carousel.setItems(items);
            carousel.setActiveItem(0);
        });
        
        /**
         * The final thing is to add a tap listener that is called whenever the user taps on the screen.
         * We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
         * those indicators moves you between items so we don't want to override that), then either hide 
         * or show the info Component.
         * 
         * Note that to hide or show this Component we're adding or removing the apod-title-visible class.
         * If you look at index.html you'll see the CSS rules style the info bar and also cause it to fade
         * in and out when you tap.
         */
        Ext.Viewport.element.on('tap', function(e) {
            if (!e.getTarget('.x-carousel-indicator')) {
                if (titleVisible) {
                    info.element.removeCls('apod-title-visible');
                    titleVisible = false;
                } else {
                    info.element.addCls('apod-title-visible');
                    titleVisible = true;
                }
            }
        });
    }
});
</code></pre>
<p>This is pretty simple stuff and you can probably just follow the comments to see what's going on. Basically though the app.js is responsible for launching our application, creating the Carousel and info Components, and setting up a couple of convenient event listeners.</p>
<p>We also had a few other files:</p>
<h2>Picture Model</h2>
<p>Found in app/model/Picture.js, our model is mostly just a list of fields sent back in the RSS feed. There is one that's somewhat more complicated than the rest though - the 'image' field. Ideally, the RSS feed would have sent back the url of the image in a separate field and we could just pull it out like any other, but alas it is embedded inside the main content.</p>
<p>To get around this, we just specify a convert function that grabs the content field, finds the first image url inside of it and pulls it out. To make sure it looks good on any device we also pass it through <a href="http://www.sencha.com/learn/how-to-use-src-sencha-io/">Sencha IO src</a>, which resizes the image to fit the screen size of whatever device we happen to be viewing it on:</p>
<pre><code class="language-javascript">/**
 * Simple Model that represents an image from NASA's Astronomy Picture Of the Day. The only remarkable
 * thing about this model is the 'image' field, which uses a regular expression to pull its value out 
 * of the main content of the RSS feed. Ideally the image url would have been presented in its own field
 * in the RSS response, but as it wasn't we had to use this approach to parse it out
 */
Ext.define('apod.model.Picture', {
    extend: 'Ext.data.Model',
    
    config: {
        fields: [
            'id', 'title', 'link', 'author', 'content',
            {
                name: 'image',
                type: 'string',
                convert: function(value, record) {
                    var content = record.get('content'),
                        regex   = /img src=\"([a-zA-Z0-9\_\.\/\:]*)\"/,
                        match   = content.match(regex),
                        src     = match[1];

                    if (src != "" &#x26;&#x26; !src.match(/\.gif$/)) {
                        src = "http://src.sencha.io/screen.width/" + src;
                    }
                    
                    return src;
                }
            }
        ]
    }
});
</code></pre>
<h2>Pictures Store</h2>
<p>Our Store is even simpler than our Model. All it does is load the APOD RSS feed over JSON-P (via Google's <a href="https://developers.google.com/feed/">RSS Feed API</a>) and decode the data with a very simple JSON Reader. This automatically pulls down the images and runs them through our Model's convert function:</p>
<pre><code class="language-javascript">/**
 * Grabs the APOD RSS feed from Google's Feed API, passes the data to our Model to decode
 */
Ext.define('apod.store.Pictures', {
    extend: 'Ext.data.Store',
    
    config: {
        model: 'apod.model.Picture',
        
        proxy: {
            type: 'jsonp',
            url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&#x26;q=http://www.acme.com/jef/apod/rss.xml&#x26;num=20',
            
            reader: {
                type: 'json',
                rootProperty: 'responseData.feed.entries'
            }
        }
    }
});
</code></pre>
<h2>Tying it all together</h2>
<p>Our app.js loads our Model and Store, plus a really simple Picture view that is basically just an <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.Img">Ext.Img</a>. All it does then is render the Carousel and Info Component to the screen and tie up a couple of listeners.</p>
<p>In case you weren't paying attention before, the info component is just an Ext.Component that we rendered up in app.js as a place to render the title of the image you're currently looking at. When you swipe between items in the carousel the <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.carousel.Carousel-event-activeitemchange">activeitemchange event</a> is fired, which we listen to near the top of app.js. All our activeitemchange listener does is update the HTML of the info component to the title of the image we just swiped to.</p>
<p>But what about the info component itself? Well at the bottom of app.js we added a tap listener on Ext.Viewport that hides or shows the info Component whenever you tap anywhere on the screen (except if you tap on the Carousel indicator icons). With a little CSS transition loveliness we get a nice fade in/out transition when we tap the screen to reveal the image title. Here's that tap listener again:</p>
<pre><code class="language-javascript">/**
 * The final thing is to add a tap listener that is called whenever the user taps on the screen.
 * We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
 * those indicators moves you between items so we don't want to override that), then either hide 
 * or show the info Component.
 */
Ext.Viewport.element.on('tap', function(e) {
    if (!e.getTarget('.x-carousel-indicator')) {
        if (titleVisible) {
            info.element.removeCls('apod-title-visible');
            titleVisible = false;
        } else {
            info.element.addCls('apod-title-visible');
            titleVisible = true;
        }
    }
});
</code></pre>
<h2>The End of the Beginning</h2>
<p>This was a really simple app that shows how easy it is to put these things together with Sencha Touch 2. Like with most stories though there's more to come so keep an eye out for parts 2 and 3 of this intergalactic adventure.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Like Android? Help us fix it]]></title>
            <link>https://edspencer.net//2012/2/6/like-android-help-us-fix-it</link>
            <guid>like-android-help-us-fix-it</guid>
            <pubDate>Mon, 06 Feb 2012 10:52:03 GMT</pubDate>
            <content:encoded><![CDATA[<p>Near the end of last week's <a href="http://www.sencha.com/blog/sencha-touch-2-raising-the-bar">Sencha Touch 2 beta release blog post</a> there was an appeal to the community to help raise awareness of a nasty flashing issue with Android 4.x phones. Every time you tried to use an animation on a web page the browser would flash, wait a bit, then finally perform the animation.</p>
<p>We filed a <a href="http://code.google.com/p/android/issues/detail?id=24833">ticket</a> on this about a week ago and thanks to your help (over 300 of you starred the issue), got a prompt response from the Android team with a fix for the flashing issue.</p>
<h2>Getting it Right</h2>
<p>However, that's only half the story. While the ugly flash is gone, animation performance on Android 4.x phones is still unacceptable. As it stands a 2 year old device running Android 2.x easily outruns the top of the range devices today running 4.x.</p>
<p>We really want to have excellent support for all Android devices. While 4.x accounts for only <a href="http://developer.android.com/resources/dashboard/platform-versions.html">1% of all Android phones today</a>, that number is only going to go up. And when it does, we want to be ready to ship fast, fluid, beautiful apps onto it.</p>
<p>So we've created <a href="http://code.google.com/p/android/issues/detail?id=25147">a new ticket with reduced, reproducible test cases and filed it to the bug tracker</a>. We'll continue to give the Android team as much support as we can in order to resolve this quickly, but once again we'll need your help.</p>
<p>In fact all we need is a few seconds of your time. Just <a href="http://code.google.com/p/android/issues/detail?id=25147">open the ticket</a> and click the star at the top left. That's all we need - it tells the Android team just how many people care about this issue and will help them prioritize it accordingly.</p>
<p>If you want to help out more, take a moment to add a comment to the ticket outlining your own experiences with this issue, like the <a href="http://code.google.com/p/android/issues/detail?id=25147#c1">m.lanyrd.com developer</a> did. Highlighting specific cases where you've had problems will really help.</p>
<h2>Thanks!</h2>
<p>Helping raise awareness of this issue will help everyone who uses or develops for Android devices on the web, and enables technologies like Sencha Touch to deliver slick, immersive apps without resorting to rewriting your app for each platform. We appreciate your help!</p>
<p><a href="http://code.google.com/p/android/issues/detail?id=25147">Star the issue now</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sencha Touch 2 Hits Beta]]></title>
            <link>https://edspencer.net//2012/2/1/sencha-touch-2-hits-beta</link>
            <guid>sencha-touch-2-hits-beta</guid>
            <pubDate>Wed, 01 Feb 2012 13:46:01 GMT</pubDate>
            <content:encoded><![CDATA[<p>Earlier today we released Sencha Touch 2 Beta 1 - check out the <a href="http://www.sencha.com/blog/sencha-touch-2-raising-the-bar">official sencha.com blog post</a> and <a href="http://dev.sencha.com/deploy/sencha-touch-2-b1/release-notes.html">release notes</a> to find out all of the awesome stuff packed into this release.</p>
<p>This is a really important release for us - Sencha Touch 2 is another huge leap forward for the mobile web and hitting beta is a massive milestone for everyone involved with the project. From a personal standpoint, working on this release with the amazing Touch team has been immensely gratifying and I hope the end result more than meets your expectations of what the mobile web can do.</p>
<p>While you should check out the official <a href="http://www.sencha.com/blog/sencha-touch-2-raising-the-bar">blog post</a> and release notes to find out the large scale changes, there are a number of things I'd really like to highlight today.</p>
<h2>A Note on Builds</h2>
<p>Before we get into the meat of B1 itself, first a quick note that we've updated the set of builds that we generate with the release. Previously there had been some confusion around which build you should be using in which circumstances so we've tried to simplify that.</p>
<p>Most people, most of the time should be using the new <em>sencha-touch-debug.js</em> while developing their app as it is unminified code that contains all of the debug warnings and comments. If you're migrating from 1.x, use the new builds/sencha-touch-all-compat.js build as it provides an easier migration path by logging additional warnings when you use 1.x-style class configurations.</p>
<p>Because we provide 5 builds in total we created <a href="http://docs.sencha.com/touch/2-0/#!/guide/building">a guide on the shipped builds and JSBuilder</a> (the tool that creates a custom build specifically for your app). The guide contains a table showing all of the options enabled for each build - hopefully that makes it easy to choose which build is best for your needs.</p>
<h2>Performance</h2>
<p>In case you haven't seen Sencha Touch 2 yet the first thing you need to know is that it's fast. Crazy fast. Check out this side by side comparison between 1.x and 2.x:</p>
<p><a href="http://vimeo.com/30296006"><img src="/images/posts/sencha-touch-2-performance-thumb.jpeg" alt="Sencha Touch 2 Performance"></a></p>
<p>Layout performance is enormously faster in 2.x due to a brand new layout engine that operates much closer to the browser's optimized CSS layout engine. The difference is pretty startling, especially on Android devices, which had sometimes struggled with Sencha Touch 1. Performance remains a top priority for us and we're really pleased with the improvements that we've secured with 2.0.</p>
<h2>Navigation View</h2>
<p>The new Navigation View is one of the slickest, sexiest things we've created for 2.0. I could play with this thing all day. If you've got a phone in your pocket or a tablet near by open up <a href="http://dev.sencha.com/deploy/sencha-touch-2-b1/examples/navigationview/">the Navigation View example</a> and see it for yourself. If you're not, check out this beautiful video of it in action:</p>
<p><a href="http://www.vimeo.com/37974749"><img src="/images/posts/getting-started-with-sencha-touch-thumb.png" alt="Getting Started with Sencha Touch 2"></a></p>
<p>Navigation Views are really easy to put together and make your application immediately come to life. Check out the <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.navigation.View">Navigation View docs</a> to see how easy it is to add this to your own applications.</p>
<h2>Awesome new examples</h2>
<p>As of beta 1 we have 24 examples shipped with the SDK, including no fewer than 6 MVC examples - Kitchen Sink, Jogs with Friends, Twitter, Kiva, Navigation View and GeoCongress.</p>
<p>The Kitchen Sink and Twitter examples also take advantage of Device Profiles, which are a powerful way to customize your app to render customized UI for tablets and phones. Take a look at the Kitchen Sink on your phone and on an iPad to see how it rearranges itself depending on the screen size.</p>
<p>Finally, if you're seeing Sencha Touch 2 for the first time you may not have seen the new inline examples in the documentation center. This is a brand new thing for Sencha Touch and allows you to edit code live on the documentation page and immediately see the results - give it a go on the <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.carousel.Carousel">Carousel docs</a>.</p>
<h2>Ludicrous Amounts of Documentation</h2>
<p>Speaking of docs, we have a stunning amount of learning material for Sencha Touch 2. We've been through all of the major classes, making sure that the functions are clearly documented and that each one has some great intro text that describes what the class does and how it fits in with the rest of the framework.</p>
<p>We've also created <a href="http://docs.sencha.com/touch/2-0/#!/guide">over 20 brand new guides</a> for Sencha Touch 2, covering everything from getting started through to developing using MVC, using Components and creating custom builds for your applications. We've put a huge amount of effort into our docs for Sencha Touch 2 and I really hope it pays off for you guys and makes it easier than ever to create great mobile web apps.</p>
<h2>Go Build Something</h2>
<p>It's only beta 1 but we're very happy with the performance, stability, API and documentation of Sencha Touch 2. I think it's the best thing we've ever created, and really highlights what the mobile web is capable of. 2012 looks set to be a very exciting year for Sencha Touch so I hope you'll join us on the adventure and build something amazing with it.</p>
<p><a href="http://cdn.sencha.io/touch/sencha-touch-2-b1.zip">Download Sencha Touch 2 Beta 1 Now</a></p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/sencha-touch-2-performance-thumb.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[The Class System in Sencha Touch 2 - What you need to know]]></title>
            <link>https://edspencer.net//2012/1/28/the-class-system-in-sencha-touch-2-what-you-need-to-know</link>
            <guid>the-class-system-in-sencha-touch-2-what-you-need-to-know</guid>
            <pubDate>Sat, 28 Jan 2012 00:53:05 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sencha Touch 1 used the class system from Ext JS 3, which provides a simple but powerful inheritance system that makes it easier to write big complex things like applications and frameworks.</p>
<p>With Sencha Touch 2 we've taken Ext JS 4's much more advanced class system and used it to create a leaner, cleaner and more beautiful framework. This post takes you through what has changed and how to use it to improve your apps.</p>
<h2>Syntax</h2>
<p>The first thing you'll notice when comparing code from 1.x and 2.x is that the class syntax is different. Back in 1.x we would define a class like this:</p>
<pre><code class="language-javascript">MyApp.CustomPanel = Ext.extend(Ext.Panel, {
    html: 'Some html'
});
</code></pre>
<p>This would create a subclass of Ext.Panel called MyApp.CustomPanel, setting the html configuration to 'Some html'. Any time we create a new instance of our subclass (by calling new MyApp.CustomPanel()), we'll now get a slightly customized Ext.Panel instance.</p>
<p>Now let's see how the same class is defined in Sencha Touch 2:</p>
<pre><code class="language-javascript">Ext.define('MyApp.CustomPanel', {
    extend: 'Ext.Panel',
    
    config: {
        html: 'Some html'
    }
});
</code></pre>
<p>There are a few changes here, let's go through them one by one. Firstly and most obviously we've swapped out Ext.extend for Ext.define. Ext.define operates using strings - notice that both 'MyApp.CustomPanel' and 'Ext.Panel' are now wrapped in quotes. This enables one of the most powerful parts of the new class system - dynamic loading.</p>
<p>I actually talked about this in a post about Ext JS 4 last year so if you're not familiar you should <a href="http://edspencer.net/2011/01/classes-in-ext-js-4-under-the-hood.html">check out the post</a>, but in a nutshell Sencha Touch 2 will automatically ensure that the class you're extending (Ext.Panel) is loaded on the page, fetching it from your server if necessary. This makes development easier and enables you to create custom builds that only contain the class your app actually uses.</p>
<p>The second notable change is that we're using a 'config' block now. Configs are a special thing in Sencha Touch 2 - they are properties of a class that can be retrieved and updated at any time, and provide extremely useful hook functions that enable you to run any custom logic you like whenever one of them is changed.</p>
<p>Whenever you want to customize any of the configurations of a subclass in Sencha Touch 2, just place them in the config block and the framework takes care of the rest, as we'll see in a moment.</p>
<h2>Consistency</h2>
<p>The biggest improvement that comes from the config system is consistency. Let's take our MyApp.CustomPanel class above and create an instance of it:</p>
<pre><code class="language-javascript">var myPanel = Ext.create('MyApp.CustomPanel');
</code></pre>
<p>Every configuration has an automatically generated getter and setter function, which we can use like this:</p>
<pre><code class="language-javascript">myPanel.setHtml('New HTML');
myPanel.getHtml(); //returns 'New HTML'
</code></pre>
<p>This might not seem much, but the convention applies to every single configuration in the entire framework. This eliminates the guesswork from the API - if you know the config name, you know how to get it and update it. Contrast this with Sencha Touch 1 where retrieving the html config meant finding some property on the instance, and updating it meant calling myPanel.update('New HTML'), which is nowhere near as predictable.</p>
<h2>Instantiating</h2>
<p>You probably noticed that we used a new function above - Ext.create. This is very similar to just calling 'new MyApp.CustomPanel()', with the exception that Ext.create uses the dynamic loading system to automatically load the class you are trying to instantiate if it is not already on the page. This can make life much easier when developing your app as you don't have to immediately manage dependencies - it just works.</p>
<p>In the example above we just instantiated a default MyApp.CustomPanel but of course we can customize it at instantiation time by passing configs into Ext.create:</p>
<pre><code class="language-javascript">var myPanel = Ext.create('MyApp.CustomPanel', {
    html: 'Some Custom HTML'
});
</code></pre>
<p>We can still call getHtml() and setHtml() to retrieve and update our html config at any time.</p>
<h2>Subclassing and Custom Configs</h2>
<p>We created a simple subclass above that provided a new default value for Ext.Panel's <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.Panel-cfg-html">html config</a>. However, we can also add our own configs to our subclasses:</p>
<pre><code class="language-javascript">Ext.define('MyApp.CustomPanel', {
    extend: 'Ext.Panel',
    
    config: {
        html: 'Some html',
        anotherConfig: 'default value'
    }
});
</code></pre>
<p>The 'anotherConfig' configuration doesn't exist on Ext.Panel so it's defined for the first time on MyApp.CustomPanel. This <em>automatically</em> creates our getter and setter functions for us:</p>
<pre><code class="language-javascript">var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns 'Something else'
</code></pre>
<p>Notice how the getter and setter names were automatically capitalized to use camelCase like all of the other functions in the framework. This was done automatically, but Sencha Touch 2 does another couple of very nice things for you - it creates hook functions:</p>
<pre><code class="language-javascript">Ext.define('MyApp.CustomPanel', {
    extend: 'Ext.Panel',
    
    config: {
        html: 'Some html',
        anotherConfig: 'default value'
    },
    
    applyAnotherConfig: function(value) {
        return "[TEST] " + value;
    },
    
    updateAnotherConfig: function(value, oldValue) {
        this.setHtml("HTML is now " + value);
    }
});
</code></pre>
<p>We've added two new functions to our class - applyAnotherConfig and updateAnotherConfig - these are both called when we call setAnotherConfig. The first one that is called is applyAnotherConfig. This is passed the value of the configuration ('default value' by default in this case) and is given the opportunity to modify it. In this case we're prepending "[TEST] " to whatever anotherConfig is set to:</p>
<pre><code class="language-javascript">var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns '[TEST] Something else'
</code></pre>
<p>The second function, updateAnotherConfig, is called after applyAnotherConfig has had a chance to modify the value and is usually used to effect some other change - whether it's updating the DOM, sending an AJAX request, or setting another config as we do here.</p>
<p>When we run the code above, as well as '[TEST] ' being prepended to our anotherConfig configuration, we're calling this.setHtml to update the html configuration too. There's no limit to what you can do inside these hook functions, just remember the rule - the apply functions are used to transform new values before they are saved, the update functions are used to perform the actual side-effects of changing the value (e.g. updating the DOM or configuring other classes).</p>
<h2>How we use it</h2>
<p>The example above is a little contrived to show the point - let's look at a <a href="http://docs.sencha.com/touch/2-0/source/Panel.html#Ext-Panel">real example</a> from Sencha Touch 2's Ext.Panel class:</p>
<pre><code class="language-javascript">applyBodyPadding: function(bodyPadding) {
    if (bodyPadding === true) {
        bodyPadding = 5;
    }

    bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);

    return bodyPadding;
},

updateBodyPadding: function(newBodyPadding) {
    this.element.setStyle('padding', newBodyPadding);
}
</code></pre>
<p>Here we see the apply and update functions for the <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.Panel-cfg-bodyPadding">bodyPadding</a> config. Notice that in the applyBodyPadding function we set a default and use the framework's unitizeBox function to parse CSS padding strings (like '5px 5px 10px 15px') into top, left, bottom and right paddings, which we then return as the transformed value.</p>
<p>The updateBodyPadding then takes this modified value and performs the actual updates - in this case setting the padding style on the Panel's element based on the new configuration. You can see similar usage in almost any component class in the framework.</p>
<h2>Find out more</h2>
<p>This is just a look through the most important aspects of the new class system and how they impact you when writing apps in Sencha Touch 2. To find out more about the class system we recommend taking a look at the <a href="http://docs.sencha.com/touch/2-0/#!/guide/class_system">Class System guide</a> and if you have any questions the <a href="http://www.sencha.com/forum/forumdisplay.php?90-Sencha-Touch-2.x-Q-amp-A">forums</a> are a great place to start.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sencha Touch 2 PR4 - Big Improvements in Data and MVC]]></title>
            <link>https://edspencer.net//2012/1/24/sencha-touch-2-pr4-big-improvements-in-data-and-mvc</link>
            <guid>sencha-touch-2-pr4-big-improvements-in-data-and-mvc</guid>
            <pubDate>Tue, 24 Jan 2012 00:59:44 GMT</pubDate>
            <content:encoded><![CDATA[<p>Today <a href="http://www.sencha.com/forum/announcement.php?f=92&#x26;a=28">we released Sencha Touch 2.0 PR4</a> - the fourth and final preview release before we hit beta. While we're technically calling this one a preview release, we're pretty happy with the performance, stability and overall quality of this release and consider it exceptionally close to beta quality.</p>
<p>As well as a good number of <a href="http://dev.sencha.com/deploy/sencha-touch-2-pr4/release-notes.html">enhancements and bug fixes</a> PR4 brings a couple of long-awaited improvements to two of the most important parts of Sencha Touch - the data package and the application architecture.</p>
<p>First up, the data package has been ported to use the new config system, which normalizes all of the configuration options for every class in the data package, providing a clean and predictable way to configure and update your data classes. We're still cleaning up some of the data package documentation and given the scope of some of the changes we're expecting a few bugs to appear as a result but overall we're very happy with the improved capabilities of Ext.data.</p>
<h2>MVC Improvements</h2>
<p>The second big improvement in PR4 is to the application architecture. The MVC classes have also been upgraded to use the new config system, again yielding big improvements in the API and general flexibility of your code.</p>
<p>History support has been baked directly into <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.app.Controller">Controllers</a>, enabling you to easily define routes that your Controller cares about, as well as the functions that handle those routes right there in your Controller file. The <a href="http://dev.sencha.com/deploy/sencha-touch-2-pr4/examples/kitchensink">Kitchen Sink example</a> has been upgraded to use routes out of the box - try it on a mobile device or desktop browser and watch how it reacts to the back/forward buttons.</p>
<p>Equally important, <a href="http://docs.sencha.com/touch/2-0/#!/guide/profiles">Device Profiles</a> have been upgraded to make creating apps that adapt to different screen sizes much simpler than ever before. Once again the <a href="http://dev.sencha.com/deploy/sencha-touch-2-pr4/examples/kitchensink">Kitchen Sink</a> has been upgraded to take advantage of device profiles. If you load it on a tablet device you'll see a split screen view with the menu on the left and the content on the right, whereas the phone version employs a nested list to save screen space.</p>
<p>To cap it off the deep linking support means you can navigate to any view on a phone, send the link to a friend on a tablet and they'll be taken to the same view customized for their screen size. As an example, try opening <a href="http://dev.sencha.com/deploy/sencha-touch-2-pr4/examples/kitchensink/#demo/forms">http://dev.sencha.com/deploy/sencha-touch-2-pr4/examples/kitchensink/#demo/forms</a> on a tablet and a phone to see it show the Forms demo specialized for each type of device.</p>
<p>As PR4 is the first time we've exposed this expanded functionality to the public we expect that there will be bugs and edge cases that crop up. We'll be keeping a close eye on the bug forums and addressing any issues as quickly as possible, as well as creating additional MVC-driven examples for you to learn from. For now, the kitchen sink is the best example of Sencha Touch 2 MVC in action.</p>
<h2>Docs</h2>
<p>We've made a huge push over the last couple of years to radically improve our documentation, and I think that even in the pre-beta PR4 release Sencha Touch 2 has the best docs we've ever created. While there are still holes to be filled in, we already ship with <a href="http://docs.sencha.com/touch/2-0/#!/guide">20 guides on how to use the framework</a>, including 4 brand new guides for PR4:</p>
<ul>
<li><a href="http://docs.sencha.com/touch/2-0/#!/guide/upgrade_1_to_2">Migrating from 1.x</a></li>
<li><a href="http://docs.sencha.com/touch/2-0/#!/guide/apps_intro">Intro to MVC</a></li>
<li><a href="http://docs.sencha.com/touch/2-0/#!/guide/profiles">Using Device Profiles</a></li>
<li><a href="http://docs.sencha.com/touch/2-0/#!/guide/controllers">Using Controllers</a></li>
</ul>
<p>As well as the guides, most of the classes now contain generous documentation explaining their function and the context in which they operate. As we move to beta and then to GA we'll be shifting our focus onto producing great demos and examples to showcase the framework's capabilities and provide realistic sample code to draw from.</p>
<p>There's a full set of <a href="http://dev.sencha.com/deploy/sencha-touch-2-pr4/release-notes.html">release notes</a> explaining the improvements in PR4 and the important known issues. We expect to be shipping regular releases from now until GA so be sure to keep an eye on the forums, twitter and the <a href="http://sencha.com/blog">sencha blog</a> for more details.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SenchaCon 2011: The Best Bits]]></title>
            <link>https://edspencer.net//2011/10/26/senchacon-2011-the-best-bits</link>
            <guid>senchacon-2011-the-best-bits</guid>
            <pubDate>Wed, 26 Oct 2011 14:46:42 GMT</pubDate>
            <content:encoded><![CDATA[<p>SenchaCon 2011 is drawing to a close and it's been another awesome ride. We were joined by 600 of the best and brightest of the Sencha community and I think it's pretty safe to say we had an awesome time. Day 3 is just drawing to a close so here's a few highlights from the week.</p>
<h2>Ext JS 4.1 Performance Preview Released</h2>
<p>There were a number of big announcements on day 1. Probably the most exciting one for me was the release of <a href="http://www.sencha.com/blog/ext-js-4-1-developer-preview">Ext JS 4.1 Performance Preview</a>. We've been working like fiends to improve Ext JS's performance profile on older browsers (IE6, IE7 and IE8 in particular) and on Monday we were able to share some of what we've achieved.</p>
<p>Page load, render and layout times are all enormously improved and have been the focus of our optimizations so far. Since 4.0 we've been building up a performance benchmarking rig that tests all of our 100+ examples (and a number of real-world customer apps) on consumer grade hardware with a range of browsers. We've seen massive improvements in loading time on these older browsers - for example the Themes Viewer example with its 300 Components all rendered at load time now starts up twice as fast as it did in 4.0.7.</p>
<p>To give a flavor for the breadth of the improvement we ran the tests on every example and summed up the loading time for each browser. As you can see below, 4.1 is able to speed through all of the examples significantly faster than 4.0.7, giving a massive performance boost across the board. It got so much faster that IE8 is now able to load all 100+ examples in a little under 20 seconds, compared with almost 60 in 4.0.7:</p>
<p><img src="/images/posts/IE-performance-on-4.0.7-vs-4.1.png" alt=""></p>
<p><a href="http://www.sencha.com/blog/ext-js-4-1-developer-preview/">See the full announcement</a> on the sencha.com blog, but like we said there, this is a pre-beta release with a number of known issues. We'd love for you to verify the speed improvements with your own apps but please don't take it anywhere near production yet! We'll have more content on what's in 4.1 in later blog posts.</p>
<h2>Other Announcements</h2>
<p>While Ext JS is closest to my heart, there were a number of other announcements made over the last few days. First up is Sencha.IO, our new cloud service and <a href="http://www.sencha.com/blog/introducing-sencha-io-the-first-mobile-html5-cloud/">now launching in beta</a>. This is a set of 4 services - data, messages, login and app deployment - that make creating and deploying web apps a snap, especially when you integrate the social aspects of Sencha.IO data and messages.</p>
<p>We also announced that we've just closed a second round of funding, raising another $15 million to further advance the state of the art in HTML5 technologies. This is going to enable us to push forward even faster and bring you some exciting new technologies. It was great that Sequoia Capital and Radar Partners were so happy with their first round with us that they decided to invest again. The future is definitely very exciting at Sencha right now.</p>
<h2>Favorite Sessions</h2>
<p>There were over 50 sessions this year and with several tracks going on simultaneously it was impossible to go to them all. Jacky Nguyen definitely stole the show with his talk on the Sencha Class System. He has a ridiculously over the top presentation style and totally brought the house down. We'll be sure to get him on stage more often!</p>
<p>Jamie and Nicolas' talk on charting was very cool and generated lots of spontaneous applause (that happened a lot during the conference, which must be a good thing), and Rob and Dave's demonstration of styling using the new beta Neptune theme was equally awesome.</p>
<p>Don lit the place up with his talk detailing the work that went into making Ext JS 4.1 so much faster, along with all the other new features in the release. Another mind blowing talk was given by John Willander, who demo'd a series of client-side attacks along with the <a href="http://beefproject.com/">BeEF Project</a>, which happens to be writen in Ext JS. Based on what John presented we'll definitely be looking at what we can do to help you secure your apps with Ext JS.</p>
<p>Of course, I had a couple of sessions myself, though a few technical problems early on made them rather more challenging than expected (it's hard to talk to people when your microphone cuts out after every second word!). The Intro to MVC talk was a blast and the sacrifice to the gods of live demos seemed to pay off as the 20 minute live coding session went without a hitch. Anyone who wants the code I put together during that session can find it <a href="https://github.com/edspencer/SenchaConDemo">up on github</a>.</p>
<h2>Meeting Everyone</h2>
<p>Although there were 600 people here this time it felt like I was able to meet almost everyone. Your intense enthusiasm for what we do really came through and to everyone who came up and gave us such great feedback it really drives us forward to keep improving your framework so thank you!</p>
<p>I saw more awesome Ext JS and Sencha Touch apps than I could count, and was pleasantly surprised to see how many people had been able to construct full applications using Sencha Touch 2 despite it only being <a href="http://www.sencha.com/blog/sencha-touch-2-developer-preview/">in Developer Preview</a> right now. It was also great getting to spend time hanging out with people and seeing them get excited when they start to see what's possible with these products. Spending time in the flesh with developers is probably the most important part of the whole conference so it was great to meet so many of you.</p>
<p>Finally, Grgur announced that the second SourceCon Europe will be taking place in London around April of next year. The first SourceCon was an awesome experience in beautiful Split, Croatia, and next year we'll be heading to London, England for this community-organized, Sencha-centric conference. They'll be launching the conference website in a couple of weeks and given how good it was last year you'll probably have to rush to get your tickets. Hope to see you there!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/IE-performance-on-4.0.7-vs-4.1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Ext JS 4.0.7 Released]]></title>
            <link>https://edspencer.net//2011/10/20/ext-js-4-0-7-released</link>
            <guid>ext-js-4-0-7-released</guid>
            <pubDate>Thu, 20 Oct 2011 12:21:38 GMT</pubDate>
            <content:encoded><![CDATA[<p>I'm very happy to report that we released Ext JS 4.0.7 to the public today. This is the seventh patch release to the 4.0.x series and contains several hundred improvements and bug fixes compared to the last public version, 4.0.2a.</p>
<p>4.0.7 is all about robustness - we've found that our support subscribers have had a lot of success with the newer builds of Ext JS 4 so I'm really pleased that we can share this with you. We're releasing this publicly earlier than we would usually do because it has taken us longer than we expected to get Ext JS 4.1 into your hands.</p>
<p>Michael put out <a href="http://www.sencha.com/blog/ext-js-4-1-update/">a post on our blog</a> last week with some updates on 4.1 and our desires around releases and communications with the community. Not being able to ship 4.1 to you yet has been a frustrating experience but I think that once you see it you'll enjoy the vast improvements it brings.</p>
<p>In the meantime, I'm happy to answer questions in the comments, via twitter or email (ed @ sencha). You can <a href="http://www.sencha.com/products/extjs/download">download the build here</a> and see the <a href="http://dev.sencha.com/deploy/ext-4.0.7-gpl/release-notes.html">full release notes for 4.0.7</a> all the way back to 4.0.0.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sencha Touch 2 - Thoughts from the Trenches]]></title>
            <link>https://edspencer.net//2011/10/11/sencha-touch-2-thoughts-from-the-trenches</link>
            <guid>sencha-touch-2-thoughts-from-the-trenches</guid>
            <pubDate>Tue, 11 Oct 2011 14:01:43 GMT</pubDate>
            <content:encoded><![CDATA[<p>As you may have seen, we put out the first public preview release of Sencha Touch 2 today. It only went live a few hours ago but the feedback has been inspiring so far. For the full scoop see <a href="http://www.sencha.com/blog/sencha-touch-2-developer-preview/">the post on the sencha.com blog</a>. A few thoughts on where we are with the product:</p>
<h2>Performance</h2>
<p>Performance on Android devices in particular is breathtaking. I never thought I'd see the day where I could pick up an Android 2.3 device and have it feel faster than an iPhone 4, and yet that's exactly what Sencha Touch 2 brings to the table. I recorded this short video on an actual device to show real world performance:</p>
<p><a href="http://vimeo.com/30324079"><img src="/images/posts/20111010-video-sm.jpg" alt="Video"></a></p>
<p>Now try the same on Sencha Touch 1.x (or any other competing framework) and (if you're anything like me) cringe at what we were accustomed to using before. That video's cool, but the one that's really driving people wild is the side by side comparison of the layout engines in 1.x and 2.x.</p>
<p>Getting our hands on a high speed camera and recording these devices at 120fps was a lot of fun. Slowing time down to 1/4 of normal speed shows just how much faster the new layout engine is than what we used to have:</p>
<p><a href="http://vimeo.com/30296006"><img src="/images/posts/sencha-touch-2-performance-thumb.jpeg" alt="Video"></a></p>
<p>The most amazing part here is that we actually finish laying out <em>before</em> the phone's rotation animation has completed. Skipping through the video frame by frame there are at least 5 frames where the app is fully laid out and interactive while the phone's rotation animation is still running. Beating the phone's own rotation speed is the holy grail - it's not possible to make it any faster.</p>
<h2>Documentation</h2>
<p>I'll admit it, I'm fanatical about <a href="http://docs.sencha.com/touch/2-0/#">great documentation</a>. I'm sure I drive everyone else on the team crazy but I think it's worth it. This is only a preview release but it already contains by far the best, most complete documentation we've ever shipped in an initial release.</p>
<p>In fact, the team's worked so hard on documenting classes that it's probably better than the (already good) <a href="http://docs.sencha.com/ext-js/4-0">Ext JS 4 docs</a>. Naturally, this makes it time to further improve the Ext JS documentation.</p>
<p>We've added some awesome features here - lots of videos, 11 brand new guides and illustrations. My favourite new feature is definitely the inline examples with live previews though - seeing Sencha Touch running live in a phone/tablet right there in the docs is just amazing. Little gems like the live twitter feed in the bottom-most example in the <a href="http://docs.sencha.com/touch/2-0/#!/api/Ext.dataview.DataView">DataView docs</a> really sell just how easy it is to configure these components.</p>
<p>We set a high bar for this though. We've gone from woeful documentation in 1.x to good documentation in 2.x, but what we're shooting for is excellence. We'll continue to round out our content over coming weeks, and have a few new features rolling out soon that will raise the bar once again.</p>
<h2>Onwards</h2>
<p>We have a few features left to implement, which is why we're calling this preview and not beta. Probably the biggest thing now is getting routing/deep linking back into the framework, along with a nice new syntax that I think you'll find really easy to use. We're also missing carousel animations and a handful of other things that will be going back in over the coming weeks. We have Sencha Con 2011 in just 12 days now though so we'll share more details there.</p>
<p>Finally though, I want to thank everyone who participated in the closed preview phase, and for everyone sending their support and kind words on the blog, the forums and on twitter. We really appreciate all the great feedback and I hope we can exceed your expectations with a fast, polished, gorgeous 2.0 final!</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/20111010-video-sm.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[SourceDevCon 2011 - an awesome conference]]></title>
            <link>https://edspencer.net//2011/5/10/sourcedevcon-2011-an-awesome-conference</link>
            <guid>sourcedevcon-2011-an-awesome-conference</guid>
            <pubDate>Tue, 10 May 2011 15:11:42 GMT</pubDate>
            <content:encoded><![CDATA[<p>The inaugural <a href="http://www.sourcedevcon.eu/">SouceDevCon</a> just wrapped up in Split, Croatia so I'd like to share a few thoughts on the last few days. The conference was an enormous success, featuring some great speakers, inspiring presentations and a fantastic group of attendees. Split itself is beautiful, and the weather was equally equitable. More than a few of us are returning a lot browner/redder than we came.</p>
<p><img src="/images/posts/sourcedevcon-grgur.jpeg" alt="Photo of the Adriatic"></p>
<h2>Day 1</h2>
<p>The conference was spread across 3 days - the first two were spent listening and learning across the three concurrent tracks, the third on a boat sailing around the Adriatic Sea. Day one kicked off with my colleagues Aditya and James setting out a little of what to expect from Sencha in 2011 in the opening keynote.</p>
<p>Straight after that I took to the stage to introduce a few of the features of Ext JS 4. My session started a little late and I forgot what I was talking about a couple of times (sorry guys :) ) but I think it turned out well enough. I spliced together the deadly combination of sleep deprivation and live coding but with a little help from the audience we were able to stumble through. I think it would make for a good screencast.</p>
<p>Aditya came on next and introduced our new Sencha.IO services, which seem to have garnered a lot of interest. James showed off the new Ext JS 4 theming support using SASS and Compass and Nils Dehl did a great job explaining the Ext.data and Ext.direct packages. Jay Garcia gave a well-received talk on creating extensions and plugins, during which I think a lot of people learned a great deal about how classes work in Ext JS 4. I also very much enjoyed Tomislav Car's investigation into getting Sencha Touch to run on phones other than Androids and iPhones.</p>
<p>Day 1 ended with a long party (I counted at least 8 hours) with inordinate amounts of Croatian beer, which went down very well. It was great meeting so many new people and hearing how much people are getting out of Ext JS and Sencha Touch, as well as what we can improve.</p>
<h2>Day 2</h2>
<p>Day 2 started in a somewhat hungover fashion with some awesome material from our very own Brian Moeskau, who demonstrated how to use the 3.x -> 4.x compatibility file, upgrading an application from 3.3.1 to 4.0.0 in front of our eyes. Nige (Animal) White demonstrated several of Ext JS's layout managers before giving one of the highlights of the conference in his debugging JavaScript presentation (by contrast he calls the introduction of errors into code "bebugging").</p>
<p>Tobias Uhlig showed off FieldManager, a sports centre management management application with a Sencha Touch mobile app, while Matz Bryntse and Brian Moeskau demoed their awesome Scheduler and Calendar components. Josef Sakalos (Saki) spent the afternoon teaching people to use Ext JS 4's new MVC package, which makes writing apps a faster and more enjoyable experience. He is an excellent teacher.</p>
<p>Our host the inimitable Grgur finished things off with a typically heartfelt ending keynote to wrap up the business end of the conference. The evening was a great opportunity to see some of Split and spend an enjoyable meal with Croatian locals Tomislav, Miro and the ever-logical Lucia. Unfortunately their attempts to teach me Croatian did not yield much success.</p>
<h2>Day 3</h2>
<p>Day 3 was a stroke of genius by Grgur. We took a chartered boat down to the town and looked around the old Roman-era palace. The boat was well stocked with beer and with so many community members in one place the conversation was pretty lively. All of this relaxing was great but with a head full of ideas I'm anxious to get back to California and better tune our products based on all the feedback I received this week.</p>
<p>All of the sessions were recorded on video and I believe they'll be made available in around a month's time. If you can't wait until then we have a meetup schedule for<a href="http://www.meetup.com/The-San-Francisco-ExtJS-Meetup-Group/events/17576007/"> May 23rd hosted at Sencha HQ</a> in northern California, to which you're all invited. Just in case you've never been to Split before perhaps the sight that greeted us when we arrived will prompt you to get yourself on a flight.</p>
<p><img src="/images/posts/adriatic.jpg" alt="Photo of the Adriatic"></p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/sourcedevcon-grgur.jpeg" length="0" type="image/jpeg"/>
        </item>
        <item>
            <title><![CDATA[Proxies in Ext JS 4]]></title>
            <link>https://edspencer.net//2011/2/2/proxies-extjs-4</link>
            <guid>proxies-extjs-4</guid>
            <pubDate>Wed, 02 Feb 2011 08:22:59 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the classes that has a lot more prominence in Ext JS 4 is the data Proxy. Proxies are responsible for all of the loading and saving of data in an Ext JS 4 or Sencha Touch application. Whenever you're creating, updating, deleting or loading any type of data in your app, you're almost certainly doing it via an Ext.data.Proxy.</p>
<p>If you've seen <a href="http://t.co/wTRxmlE">January's Sencha newsletter</a> you may have read an article called <a href="http://www.sencha.com/blog/ext-js-4-anatomy-of-a-model/">Anatomy of a Model</a>, which introduces the most commonly-used Proxies. All a Proxy really needs is four functions - create, read, update and destroy. For an AjaxProxy, each of these will result in an Ajax request being made. For a LocalStorageProxy, the functions will create, read, update or delete records from HTML5 localStorage.</p>
<p>Because Proxies all implement the same interface they're completely interchangeable, so you can swap out your data source - at design time or run time - without changing any other code. Although the local Proxies like LocalStorageProxy and MemoryProxy are self-contained, the remote Proxies like AjaxProxy and ScriptTagProxy make use of Readers and Writers to encode and decode their data when communicating with the server.</p>
<p><img src="/images/posts/Proxy-Reader-and-Writer.png" alt="Ext.data.Proxy Reader and Writer"></p>
<p>Whether we are reading data from a server or preparing data to be sent back, usually we format it as either JSON or XML. Both of our frameworks come with JSON and XML Readers and Writers which handle all of this for you with a very simple API.</p>
<h3>Using a Proxy with a Model</h3>
<p>Proxies are usually used along with either a Model or a Store. The simplest setup is just with a model:</p>
<pre><code class="language-javascript">var User = Ext.regModel('User', {
    fields: ['id', 'name', 'email'],
    
    proxy: {
        type: 'rest',
        url : '/users',
        reader: {
            type: 'json',
            root: 'users'
        }
    }
});
</code></pre>
<p>Here we've created a User model with a RestProxy. RestProxy is a special form of AjaxProxy that can automatically figure out Restful urls for our models. The Proxy that we set up features a JsonReader to decode any server responses - check out the recent <a href="http://www.sencha.com/blog/2011/01/21/countdown-to-ext-js-4-data-package/">data package post</a> on the Sencha blog to see Readers in action.</p>
<p>When we use the following functions on the new User model, the Proxy is called behind the scenes:</p>
<pre><code class="language-javascript">var user = new User({name: 'Ed Spencer'});

//CREATE: calls the RestProxy's create function because the user has never been saved
user.save();

//UPDATE: calls the RestProxy's update function because it has been saved before
user.set('email', 'ed@sencha.com');

//DESTROY: calls the RestProxy's destroy function
user.destroy();

//READ: calls the RestProxy's read function
User.load(123, {
    success: function(user) {
        console.log(user);
    }
});
</code></pre>
<p>We were able to perform all four CRUD operations just by specifying a Proxy for our Model. Notice that the first 3 calls are instance methods whereas the fourth (User.load) is static on the User model. Note also that you can create a Model without a Proxy, you just won't be able to persist it.</p>
<h3>Usage with Stores</h3>
<p>In Ext JS 3.x, most of the data manipulation was done via Stores. A chief purpose of a Store is to be a local subset of some data plus delta. For example, you might have 1000 products in your database and have 25 of them loaded into a Store on the client side (the local subset). While operating on that subset, your user may have added, updated or deleted some of the Products. Until these changes are synchronized with the server they are known as a delta.</p>
<p>In order to read data from and sync to the server, Stores also need to be able to call those CRUD operations. We can give a Store a Proxy in the same way:</p>
<pre><code class="language-javascript">var store = new Ext.data.Store({
    model: 'User',
    proxy: {
        type: 'rest',
        url : '/users',
        reader: {
            type: 'json',
            root: 'users'
        }
    }
});
</code></pre>
<p>We created the exact same Proxy for the Store because that's how our server side is set up to deliver data. Because we'll usually want to use the same Proxy mechanism for all User manipulations, it's usually best to just define the Proxy once on the Model and then simply tell the Store which Model to use. This automatically picks up the User model's Proxy:</p>
<pre><code class="language-javascript">//no need to define proxy - this will reuse the User's Proxy
var store = new Ext.data.Store({
    model: 'User'
});
</code></pre>
<p>Store invokes the CRUD operations via its load and sync functions. Calling load uses the Proxy's read operation, which sync utilizes one or more of create, update and destroy depending on the current Store delta.</p>
<pre><code class="language-javascript">//CREATE: calls the RestProxy's create function to create the Tommy record on the server
store.add({name: 'Tommy Maintz'});
store.sync();

//UPDATE: calls the RestProxy's update function to update the Tommy record on the server
store.getAt(1).set('email', 'tommy@sencha.com');
store.sync();

//DESTROY: calls the RestProxy's destroy function
store.remove(store.getAt(1));
store.sync();

//READ: calls the RestProxy's read function
store.load();
</code></pre>
<p>Store has used the exact same CRUD operations on the shared Proxy. In all of the examples above we have used the exact same RestProxy instance from three different places: statically on our Model (User.load), as a Model instance method (user.save, user.destroy) and via a Store instance (store.load, store.sync):</p>
<p><img src="/images/posts/Proxy-reuse.png" alt="Data Proxy Reuse"></p>
<p>Of course, most Proxies have their own private methods to do the actual work, but all a Proxy needs to do is implement those four functions to be usable with Ext JS 4 and Sencha Touch. This means it's easy to create new Proxies, as James Pearce did in a <a href="http://www.sencha.com/learn/Tutorial:A_Sencha_Touch_MVC_application_with_PhoneGap">recent Sencha Touch example</a> where he needed to read address book data from a mobile phone. Everything he does to set up his Proxy <a href="http://www.sencha.com/learn/Tutorial:A_Sencha_Touch_MVC_application_with_PhoneGap">in the article</a> (about 1/3rd of the way down) works the same way for Ext JS 4 too.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/Proxy-Reader-and-Writer.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introduction to Ext JS 4]]></title>
            <link>https://edspencer.net//2011/1/27/introducing-ext-js-4</link>
            <guid>introducing-ext-js-4</guid>
            <pubDate>Thu, 27 Jan 2011 08:30:20 GMT</pubDate>
            <content:encoded><![CDATA[<p>At the end of last 2010 we capped off an incredible year with SenchaCon - by far the biggest gathering of Sencha developers ever assembled. We descended on San Francisco, 500 strong, and spent an amazing few days sharing the awesome new stuff we're working on, learning from each other, and addressing the web's most pressing problems.</p>
<p>Now, we're proud to release <a href="http://www.sencha.com/conference/sessions/videos.php">all of the videos</a> from the conference completely free for everyone. You can see a <a href="http://www.sencha.com/conference/sessions/videos.php">full list</a> on our conference site, where you'll find days worth of material all about Ext JS 4, Sencha Touch and all of the other treats we're working on at the moment.</p>
<p>Some of the videos in particular stand out for me - Jamie's <a href="http://vimeo.com/17673342">Charting</a> and <a href="http://vimeo.com/17917111">Layouts</a> talks were spectacular, as was Rob's <a href="http://vimeo.com/19159630">Theming Ext JS 4</a> talk. On the Touch side, Tommy's talks on <a href="http://vimeo.com/17699976">Performance</a> and <a href="http://vimeo.com/17853133">Debugging</a> are required viewing, as is Dave Kaneda's characteristically off the cuff <a href="http://vimeo.com/17705448">Theming</a> talk.</p>
<p>My personal high point was standing in front of all of you and <a href="https://vimeo.com/17666102">introducing Ext JS 4</a> and its three core goals - speed, stability and ease of use. I think you're going to love what we've done with the framework in version 4.</p>
<p>If you're so inclined, you can find the <a href="http://www.slideshare.net/edspencer/intro-to-ext-4">slides for this talk</a> on slideshare, and if you can still stand the sound of my voice check out my other presentation on <a href="http://vimeo.com/17733892">Ext JS 4 Architecture</a>, focusing chiefly on the new data package (<a href="http://www.slideshare.net/edspencer/ext-js-4-architecture">slides</a>).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext JS 4: The Class Definition Pipeline]]></title>
            <link>https://edspencer.net//2011/1/25/ext-js-4-the-class-definition-pipeline</link>
            <guid>ext-js-4-the-class-definition-pipeline</guid>
            <pubDate>Tue, 25 Jan 2011 08:25:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Last time, we looked at some of the features of the <a href="http://edspencer.net/2011/01/classes-in-ext-js-4-under-the-hood.html">new class system in Ext JS 4</a>, and explored some of the code that makes it work. Today we're going to dig a little deeper and look at the class definition pipeline - the framework responsible for creating every class in Ext JS 4.</p>
<p><a href="http://edspencer.net/2011/01/classes-in-ext-js-4-under-the-hood.html">As I mentioned last time</a>, every class in Ext JS 4 is an instance of Ext.Class. When an Ext.Class is constructed, it hands itself off to a pipeline populated by small, focused processors, each of which handles one part of the class definition process. We ship a number of these processors out of the box - there are processors for handling mixins, setting up configuration functions and handling class extension.</p>
<p>The pipeline is probably best explained with a picture. Think of your class starting its definition journey at the bottom left, working its way up the preprocessors on the left hand side and then down the postprocessors on the right, until finally it reaches the end, where it signals its readiness to a callback function:</p>
<p><a href="/images/posts/processors.png"><img src="/images/posts/processors.png" alt=""></a></p>
<p>The distinction between preprocessors and postprocessors is that a class is considered ‘ready’ (e.g. can be instantiated) after the preprocessors have all been executed. Postprocessors typically perform functions like aliasing the class name to an xtype or back to a legacy class name - things that don't affect the class' behavior.</p>
<p>Each processor runs asynchronously, calling back to the Ext.Class constructor when it is ready - this is what enables us to extend classes that don’t exist on the page yet. The first preprocessor is the Loader, which checks to see if all of the new Class’ dependencies are available. If they are not, the Loader can dynamically load those dependencies before calling back to Ext.Class and allowing the next preprocessor to run. We'll take another look at the Loader in another post.</p>
<p>After running the Loader, the new Class is set up to inherit from the declared superclass by the Extend preprocessor. The Mixins preprocessor takes care of copying all of the functions from each of our mixins, and the Config preprocessor handles the creation of the 4 config functions we saw last time (e.g. getTitle, setTitle, resetTitle, applyTitle - check out <a href="http://edspencer.net/2011/01/classes-in-ext-js-4-under-the-hood.html">yesterday's post</a> to see how the Configs processor helps out).</p>
<p>Finally, the Statics preprocessor looks for any static functions that we set up on our new class and makes them available statically on the class. The processors that are run are completely customizable, and it’s easy to add custom processors at any point. Let's take a look at that Statics preprocessor as an example:</p>
<pre><code class="language-javascript">//Each processor is passed three arguments - the class under construction,
//the configuration for that class and a callback function to call when the processor has finished
Ext.Class.registerPreprocessor('statics', function(cls, data, callback) {
    if (Ext.isObject(data.statics)) {
        var statics = data.statics,
            name;
        
        //here we just copy each static function onto the new Class
        for (name in statics) {
            if (statics.hasOwnProperty(name)) {
                cls[name] = statics[name];
            }
        }
    }

    delete data.statics;

    //Once the processor's work is done, we just call the callback function to kick off the next processor
    if (callback) {
        callback.call(this, cls, data);
    }
});

//Changing the order that the preprocessors are called in is easy too - this is the default
Ext.Class.setDefaultPreprocessors(['extend', 'mixins', 'config', 'statics']);
</code></pre>
<p>What happens above is pretty straightforward. We're registering a preprocessor called 'statics' with Ext.Class. The function we provide is called whenever the 'statics' preprocessor is invoked, and is passed the new Ext.Class instance, the configuration for that class, and a callback to call when the preprocessor has finished its work.</p>
<p>The actual work that this preprocessor does is trivial - it just looks to see if we declared a 'statics' property in our class configuration and if so copies it onto the new class. For example, let's say we want to create a static getNextId function on a class:</p>
<pre><code class="language-javascript">Ext.define('MyClass', {
    statics: {
        idSeed: 1000,
        getNextId: function() {
            return this.idSeed++;
        }
    }
});
</code></pre>
<p>Because of the Statics preprocessor, we can now call the function statically on the Class (e.g. without creating an instance of MyClass):</p>
<pre><code class="language-javascript">MyClass.getNextId(); //1000
MyClass.getNextId(); //1001
MyClass.getNextId(); //1002
... etc
</code></pre>
<p>Finally, let's come back to that callback at the bottom of the picture above. If we supply one, a callback function is run after all of the processors have run. At this point the new class is completely ready for use in your application. Here we create an instance of MyClass using the callback function, guaranteeing that the dependency on Ext.Window has been honored:</p>
<pre><code class="language-javascript">Ext.define('MyClass', {
    extend: 'Ext.Window'
}, function() {
   //this callback is called when MyClass is ready for use
   var cls = new MyClass();
   cls.setTitle('Everything is ready');
   cls.show();
});
</code></pre>
<p>That's it for today. Next time we'll look at some of the new features in the part of Ext JS 4 that is closest to my heart - the data package.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/processors.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Classes in Ext JS 4: Under the hood]]></title>
            <link>https://edspencer.net//2011/1/24/classes-in-ext-js-4-under-the-hood</link>
            <guid>classes-in-ext-js-4-under-the-hood</guid>
            <pubDate>Mon, 24 Jan 2011 08:37:22 GMT</pubDate>
            <content:encoded><![CDATA[<p>Last week we unveiled a the brand new class system coming in Ext JS 4. If you haven’t seen the new system in action I hope you’ll take a look at the <a href="http://www.sencha.com/blog/2011/01/19/countdown-to-ext-js-4-dynamic-loading-and-new-class-system/">blog post</a> on sencha.com and check out the <a href="http://dev.sencha.com/deploy/LoaderDemo/">live demo</a>. Today we’re going to dig a little deeper into the class system to see how it actually works.</p>
<p>To briefly recap, the new class system enables us to define classes like this:</p>
<pre><code class="language-javascript">Ext.define('Ext.Window', {
    extend: 'Ext.Panel',
    requires: 'Ext.Tool',
    mixins: {
        draggable: 'Ext.util.Draggable'
    },
    
    config: {
        title: "Window Title"
    }
});
</code></pre>
<p>Here we’ve set up a slightly simplified version of the Ext.Window class. We’ve set Window up to be a subclass of Panel, declared that it requires the Ext.Tool class and that it mixes in functionality from the Ext.util.Draggable class.</p>
<p>There are a few new things here so we’ll attack them one at a time. The ‘extend’ declaration does what you’d expect - we’re just saying that Window should be a subclass of Panel. The ‘requires’ declaration means that the named classes (just Ext.Tool in this case) have to be present before the Window class can be considered ‘ready’ for use (more on class readiness in a moment).</p>
<p>The ‘mixins’ declaration is a brand new concept when it comes to Ext JS. A mixin is just a set of functions (and sometimes properties) that are merged into a class. For example, the Ext.util.Draggable mixin we defined above might contain a function called ‘startDragging’ - this gets copied into Ext.Window to enable us to use the function in a window instance:</p>
<pre><code class="language-javascript">//a simplified Draggable mixin
Ext.define('Ext.util.Draggable', {
    startDragging: function() {
        console.log('started dragging');
    }
});
</code></pre>
<p>When we create a new Ext.Window instance now, we can call the function that was mixed in from Ext.util.Draggable:</p>
<pre><code class="language-javascript">var win = Ext.create('Ext.Window');
win.startDragging(); //"started dragging"
</code></pre>
<p>Mixins are really useful when a class needs to inherit multiple traits but can’t do so easily using a traditional single inheritance mechanism. For example, Ext.Windows is a draggable component, as are Sliders, Grid headers, and many other UI elements. Because this behavior crops up in many different places it’s not feasible to work the draggable behavior into a single superclass because not all of those UI elements actually share a common superclass. Creating a Draggable mixin solves this problem - now anything can be made draggable with a couple of lines of code.</p>
<p>The last new piece of functionality I’ll mention briefly is the ‘config’ declaration. Most of the classes in Ext JS take configuration parameters, many of which can be changed at runtime. In the Ext.Window above example we declared that the class has a ‘title’ configuration, which takes the default value of ‘Window Title’. By setting the class up like this we get 4 methods for free - getTitle, setTitle, resetTitle and applyTitle.</p>
<ul>
<li><strong>getTitle</strong> - returns the current title</li>
<li><strong>setTitle</strong> - sets the title to a new value</li>
<li><strong>resetTitle</strong> -  reverts the title to its default value (‘Window Title’)</li>
<li><strong>applyTitle</strong> - this is a template method that you can choose to define. It is called whenever setTitle is called.</li>
</ul>
<p>The applyTitle function is the place to put any logic that needs to be called when the title is changed - for example we might want to update a DOM Element with the new title:</p>
<pre><code class="language-javascript">Ext.define(‘Ext.Window’, {
    //..as above,
    
    config: {
        title: 'Window Title'
    },
    
    //updates the DOM element that contains the window title
    applyTitle: function(newTitle) {
        this.titleEl.update(newTitle);
    }
});
</code></pre>
<p>This saves us a lot of time and code while providing a consistent API for all configuration options: win-win.</p>
<h3>Digging Deeper</h3>
<p>Ext JS 4 introduces 4 new classes to make all this magic work:</p>
<ul>
<li><strong>Ext.Base</strong> - all classes inherit from Ext.Base. It provides basic low-level functionality used by all classes</li>
<li><strong>Ext.Class</strong> - a factory for making new classes</li>
<li><strong>Ext.ClassLoader</strong> - responsible for ensuring that classes are available, loading them if they aren’t on the page already</li>
<li><strong>Ext.ClassManager</strong> - kicks off class creation and manages dependencies</li>
</ul>
<p>These all work together behind the scenes and most of the time you won’t even need to be aware of what is being called when you define and use a class. The two functions that you’ll use most often - Ext.define and Ext.create - both call Ext.ClassManager under the hood, which in turn utilizes the other three classes to put everything together.</p>
<p>The distinction between Ext.Class and Ext.Base is important. Ext.Base is the top-level <em>superclass</em> for every class ever defined - every class inherits from Ext.Base at some point. Ext.Class represents the class itself - every class you define is an <em>instance</em> of Ext.Class, and a <em>subclass</em> of Ext.Base. To illustrate, let’s say we created a class called MyClass, which doesn’t extend any other class:</p>
<pre><code class="language-javascript">Ext.define('MyClass', {
    someFunction: function() {
        console.log('Ran some function');
    }
});
</code></pre>
<p>The direct superclass for MyClass is Ext.Base because we didn’t specify that MyClass should extend anything else. If you imagine a tree of all the classes we’ve defined so far, it will look something like this:</p>
<p><img src="/images/posts/Sample-inheritance-tree.png" alt="Sample Inheritance Tree"></p>
<p>This tree bases its hierarchy on the inheritance structure of our classes, and the root is always Ext.Base - that is, every class eventually inherits from Ext.Base. So every item in the diagram above is a <em>subclass</em> of Ext.Base, but every item is also an <em>instance</em> of Ext.Class. Classes themselves are instances of Ext.Class, which means we can easily modify the Class at a later time - for example mixing in additional functionality:</p>
<pre><code class="language-javascript">//we can define some mixins at definition time
Ext.define('MyClass', {
    mixins: {
        observable: 'Ext.util.Observable'
    }
});

//it’s easy to add more later too
MyClass.mixin('draggable', 'Ext.util.Draggable');
</code></pre>
<p>This architecture opens up new possibilities for dynamic class creation and metaprogramming, which were difficult to pull off in earlier versions.</p>
<p>In the next episode, we’ll look at how the class definition pipeline is structured and how to extend it to add your own features.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/Sample-inheritance-tree.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sencha Touch tech talk at Pivotal Labs]]></title>
            <link>https://edspencer.net//2010/9/27/sencha-touch-tech-talk-at-pivotal-labs</link>
            <guid>sencha-touch-tech-talk-at-pivotal-labs</guid>
            <pubDate>Mon, 27 Sep 2010 12:46:58 GMT</pubDate>
            <content:encoded><![CDATA[<p>I recently gave an <a href="http://pivotallabs.com/talks/109-sencha-touch">introduction to Sencha Touch</a> talk up at Pivotal Labs in San Francisco. The guys at Pivotal were kind enough to record this short talk and share it with the world - it's under 30 minutes and serves as a nice, short introduction to Sencha Touch:</p>
<p><strong>UPDATE</strong>: Pivotal got acquired, this link broke. The world moved on.</p>
<p>The slides are available on slideshare and include the code snippets I presented. The Dribbble example used in the talk is very similar to the <a href="http://kivamobile.org/">Kiva example</a> that ships with the <a href="http://www.sencha.com/products/touch/">Sencha Touch SDK</a>, so I recommend checking that out if you want to dive in further.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the Ext JS PivotGrid]]></title>
            <link>https://edspencer.net//2010/7/29/using-the-ext-js-pivotgrid</link>
            <guid>using-the-ext-js-pivotgrid</guid>
            <pubDate>Thu, 29 Jul 2010 12:10:13 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the new components we just unveiled for the <a href="http://www.sencha.com/blog/2010/07/28/announcing-ext-js-3-3-beta-pivotgrids-calendars-and-more/">Ext JS 3.3 beta</a> is PivotGrid. PivotGrid is a powerful new component that reduces and aggregates large datasets into a more understandable form.</p>
<p>A classic example of PivotGrid's usefulness is in analyzing sales data. Companies often keep a database containing all the sales they have made and want to glean some insight into how well they are performing. PivotGrid gives the ability to rapidly summarize this large and unwieldy dataset - for example showing sales count broken down by city and salesperson.</p>
<h2>A simple example</h2>
<p>We created <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/simple.html">an example of this scenario</a> in the 3.3 beta release. Here we have a fictional dataset containing 300 rows of sales data (<a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/simple.json">see the raw data</a>). We asked PivotGrid to break the data down by Salesperson and Product, showing us how they performed over time. Each cell contains the sum of sales made by the given salesperson/product combination in the given city and year.</p>
<p>Let's see how we create this PivotGrid:</p>
<pre><code class="language-javascript">var SaleRecord = Ext.data.Record.create([
    {name: 'person',   type: 'string'},
    {name: 'product',  type: 'string'},
    {name: 'city',     type: 'string'},
    {name: 'state',    type: 'string'},
    {name: 'month',    type: 'int'},
    {name: 'quarter',  type: 'int'},
    {name: 'year',     type: 'int'},
    {name: 'quantity', type: 'int'},
    {name: 'value',    type: 'int'}
]);

var myStore = new Ext.data.Store({
    url: 'salesdata.json',
    autoLoad: true,
    reader: new Ext.data.JsonReader({
        root: 'rows',
        idProperty: 'id'
    }, SaleRecord)
});

var pivotGrid = new Ext.grid.PivotGrid({
    title     : 'Sales Performance',
    store     : myStore,
    aggregator: 'sum',
    measure   : 'value',
    
    leftAxis: [
        {dataIndex: 'person',  width: 80},
        {dataIndex: 'product', width: 90}
    ],
    
    topAxis: [
        {dataIndex: 'year'},
        {dataIndex: 'city'}
    ]
});
</code></pre>
<p>The first half of this ought to be very familiar - we just set up a normal Record and Store. This is all we need to load our <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/simple.json">sample data</a> so that it's ready for pivoting. This is all exactly the same code as for our other Store-bound components like Grid and DataView so it's easy to take an existing Grid and turn it into a PivotGrid.</p>
<p>The second half of the code creates the PivotGrid itself. There are 5 main components to a PivotGrid - the store, the measure, the aggregator, the left axis and the top axis. Taking these in turn:</p>
<ul>
<li><strong>Store</strong> - the Store we created above</li>
<li><strong>Measure</strong> - the field in the data that we want to aggregate (in this case the sale value)</li>
<li><strong>Aggregator</strong> - the function we use to combine data into the cells. <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/docs/?class=Ext.grid.PivotGrid">See the docs</a> for full details</li>
<li><strong>Left Axis</strong> - the fields to break data down by on the left axis</li>
<li><strong>Top Axis</strong> - the fields to break data down by on the top axis</li>
</ul>
<p>The measure and the items in the axes must all be fields from the Store. The aggregator function can usually be passed in as a string - there are 5 aggregator functions built in: sum, count, min, max and avg.</p>
<h2>Renderers</h2>
<p>This is all we need to create a simple PivotGrid; now it's time to look at a few more advanced options. Let's start with renderers. Once the data for each cell has been calculated, the value is passed to an optional renderer function, which takes each value in turn and returns another value. <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/people.html">One of the PivotGrid examples</a> shows average heights in feet and inches but the calculated data is in decimal. Here's the renderer we use in that example:</p>
<pre><code class="language-javascript">new Ext.grid.PivotGrid({
    store     : myStore,
    aggregator: 'avg',
    measure   : 'height',
    
    //turns a decimal number of feet into feet and inches
    renderer  : function(value) {
        var feet   = Math.floor(value),
            inches = Math.round((value - feet) * 12);
            
        return String.format("{0}' {1}"", feet, inches);
    },
    //the rest of the config
});
</code></pre>
<h2>Customising cell appearance</h2>
<p>Another one of the <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/countries.html">PivotGrid examples uses a custom cell style</a>. As with the renderer, each cell has the opportunity to alter itself with a custom function - here's the one we use in the <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/countries.html">countries example</a>:</p>
<pre><code class="language-javascript">new Ext.grid.PivotGrid({
    store     : myStore,
    aggregator: 'avg',
    measure   : 'height',
    
    viewConfig: {
        getCellCls: function(value) {
            if (value &#x3C; 20) {
                return 'expense-low';
            } else if (value &#x3C; 75) {
                return 'expense-medium';
            } else {
                return 'expense-high';
            }
        }
    },
    //the rest of the config
});
</code></pre>
<h2>Reconfiguring at runtime</h2>
<p>A lot of the power of PivotGrid is that it can be used by users of your application to summarize datasets any way they want. This is made possible by PivotGrid's ability to reconfigure itself at runtime. We present one final example of a <a href="http://www.sencha.com/deploy/ext-3.3-beta1-6976/examples/pivotgrid/reconfigurable.html">PivotGrid that can be reconfigured at runtime</a>. Here's how we perform the reconfiguration:</p>
<pre><code class="language-javascript">//the left axis can also be changed
pivot.topAxis.setDimensions([
    {dataIndex: 'city', direction: 'DESC'},
    {dataIndex: 'year', direction: 'ASC'}
]);

pivot.setMeasure('value');
pivot.setAggregator('avg');

pivot.view.refresh(true);
</code></pre>
<p>It's easy to change the axes, dimension, aggregator and measure at any time and then refresh the data. The calculations are all performed client side so there is no need for another round-trip to the server when reconfiguring. The example linked above gives an example interface for updating a PivotGrid, though anything that can make the API calls above could be used.</p>
<p>I hope you enjoy the new components in this Ext JS 3.3 beta and look forward to comments and suggestions. Although we're only at beta stage I think the additions are already quite robust so feel free to stress-test them.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Offline Apps with HTML5: A case study in Solitaire]]></title>
            <link>https://edspencer.net//2010/6/21/offline-apps-with-html5-a-case-study-in-solitaire</link>
            <guid>offline-apps-with-html5-a-case-study-in-solitaire</guid>
            <pubDate>Mon, 21 Jun 2010 08:43:53 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of my contributions to the newly-launched <a href="http://www.sencha.com/products/touch/">Sencha Touch</a> mobile framework is the <a href="http://touchsolitaire.mobi/">Touch Solitaire</a> game. This is not the first time I have ventured into the dizzying excitement of Solitaire game development; you may remember the wonderful <a href="http://solitaire.edspencer.net/">Ext JS Solitaire</a> from 18 months ago. I'm sure you'll agree that the new version is a small improvement.</p>
<p><img src="/images/posts/solitaire.gif" alt="Solitaire"></p>
<p>Solitaire is a nice example of a fun application that can be written with Sencha Touch. It makes use of the provided Draggables and Droppables, CSS-based animations, the layout manager and the brand new data package. The great thing about a game like this though is that it can be run entirely offline. Obviously this is simple with a native application, but what about a web app? Our goal is not just having the game able to run offline, but to save your game state locally too.</p>
<p>The answer comes in two parts:</p>
<h2>Web Storage and the Sencha data package</h2>
<p>HTML5 provides a brand new API called Web Storage for storing data locally. You can read all about it on my <a href="http://www.sencha.com/blog/2010/05/27/the-html5-family-web-storage/">Web Storage post on Sencha's blog</a> but the summary is that you can store string data locally in the browser and retrieve it later, even if the browser or the user's computer had been restarted in the meantime.</p>
<p>The crucial part of the sentence above is that we can only store string data. In the case of a game of Solitaire we need to store data on the elapsed time and number of moves as well as the location and status of each card. This doesn't sound like the kind of data we want to manually encode into a string, so thankfully the data package comes to the rescue.</p>
<p>The Sencha Touch data package is a complete rewrite of the package that has been so successful in powering Ext JS 3.x. It shares many of the same philosophies and adds the learning we have gained from developing Ext JS 3.x over the past year. One of the new capabilities it offers us is a <a href="http://www.sencha.com/deploy/touch/docs/?class=Ext.data.LocalStorageProxy">Local Storage proxy</a>, which automatically marshalls your model data into local storage and transparently restores it when you need it.</p>
<p>Using the new proxy is simple - all we need to do is set up a new Store, specifying the Proxy and the Model that will be saved to it. Models are the spiritual successor to Ext JS 3.x's Records. Now whenever we add, remove or update model instances in the store they are automatically saved to localStorage for us. Loading the store again is equally easy:</p>
<pre><code class="language-javascript">//set the store up
var gameStore = new Ext.data.Store({
    proxy: new Ext.data.LocalStorageProxy({
        id: 'solitaire-games'
    }),
    model: 'Game'
});

//saves all outstanding modifications, deletions or creations to localStorage
gameStore.sync();

//load our saved games
gameStore.read({
    scope: this,
    callback: function(records) {
        //code to load the first record
    }
});
</code></pre>
<p>And just like that we can save and restore games with Web Storage. We can visit our app's webpage and start a game then come back later and find it automatically restored. But we still can't play offline, for that we need the application cache.</p>
<h2>The HTML5 Application Cache Manifest</h2>
<p>The application cache is one of the best features of HTML5. It provides a simple (though sometimes frustrating) way of telling the browser about all of the files your application relies on so that it can download them all ready for offline use. All you have to do is create what's known as a manifest file which lists all of the files the application needs - the Solitaire manifest looks like this:</p>
<pre><code>CACHE MANIFEST
#rev49

resources/icon.png
resources/loading.png

resources/themes/wood/board.jpg
resources/themes/wood/cards.png

resources/css/ext-touch.css
resources/solitaire-notheme.css
resources/themes/wood/wood.css
resources/themes/metal/metal.css

ext-touch-debug.js
solitaire-all-debug.js
</code></pre>
<p>We tell the browser about the manifest file by pointing to it in the  tag's manifest atttibute. When the browser finds this file it downloads each of the listed assets so that they are ready for offline consumption. Note that it does not automatically include them on the page, you still need to do that yourself via the usual link and script tags. Here's a snippet of the Solitaire index.html file:</p>
<pre><code class="language-html">&#x3C;!doctype html>
&#x3C;html manifest="solitaire.manifest">
    &#x3C;head>
        &#x3C;meta http-equiv="Content-Type" content="text/html; charset=utf-8">	
        &#x3C;title>Solitaire&#x3C;/title>

        &#x3C;link rel="stylesheet" href="resources/css/ext-touch.css" type="text/css">
        &#x3C;link rel="stylesheet" href="resources/solitaire-notheme.css" type="text/css">
        &#x3C;link rel="stylesheet" href="resources/themes/wood/wood.css" type="text/css">

        &#x3C;script type="text/javascript" src="ext-touch-debug.js">&#x3C;/script>
        &#x3C;script type="text/javascript" src="solitaire-all-debug.js">&#x3C;/script>
</code></pre>
<p>Note the manifest file definition in the html element at the top, and the fact that we still include our page resources the normal way. It sounds easy, but without a little setup first it can be a very frustrating experience. Usually your browser will try to cache as many files as possible, including the manifest file itself - we don't want this. As soon as your browser has a long-term cache of the manifest file it is extremely difficult to update your application - all of the files are already offline and won't be updated, and the browser won't even ask the server for an updated manifest file.</p>
<p>Preventing this behaviour turns out to be fairly easy, and the solution in its simplest form comes in the shape of a .htaccess file with contents like the following:</p>
<pre><code>&#x3C;Files solitaire.manifest> 
    ExpiresActive On 
    ExpiresDefault "access" 
&#x3C;/Files>
</code></pre>
<p>This directs Apache to tell the browser not to cache the manifest file at all, instead requesting the file from the server on every page load. Note that if the device is currently offline it will use the last manifest file it received.</p>
<p>This is half the battle won, but let's say you change one of your application files and reload - you'll find nothing happened. This is because when your browser asked the server for the manifest file it actually asked if the file had changed or not. As the manifest itself wasn't updated, the server responds with a 304 (Not Modified) and your browser keeps the old file.</p>
<p>To make the browser pick up on the change to the application file you need to update the manifest file itself. This is where the mysterious "#rev49" comes in on the manifest example file above. This is a suggestion from the <a href="http://diveintohtml5.org/offline.html">excellent diveintohtml5 article</a> on the subject - whenever you change any application files just bump up the revision number in the manifest file and your browser will know to download the updated files.</p>
<p>One final detail is that your Apache server probably isn't set up to server manifest files with the correct mime type, so be sure to add the following line to your Apache config and restart the server:</p>
<pre><code>AddType text/cache-manifest .manifest  
</code></pre>
<h2>Wrapping it up</h2>
<p>Offline access is a big deal for mobile apps and Sencha Touch makes them much easier to write. The benefit is not so much that the apps can run without an internet connection (many modern touch devices have a near-permanent connection to the internet already), but that web apps can now be treated as first-class citizens alongside native apps.</p>
<p>The fact that many devices allow your users to save your app to their home screen and load it as though it were native is an important step - you keep all of the advantages of web app deployment while gaining some of the benefits of native apps. As more and more native hardware APIs become available to web apps their importance will only grow.</p>
<p>If you want to check out Solitaire's offline support for yourself <a href="http://touchsolitaire.mobi/app/">visit the application's site</a> and save it to your iPad's home page. Try turning on airplane mode and loading the app and see how it behaves as though it were native. If you don't have an iPad, you can load the app in up-to-date versions of Chrome or Safari and get a similar experience.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/solitaire.gif" length="0" type="image/gif"/>
        </item>
        <item>
            <title><![CDATA[Writing Compressible JavaScript]]></title>
            <link>https://edspencer.net//2010/6/19/writing-compressible-javascript</link>
            <guid>writing-compressible-javascript</guid>
            <pubDate>Sat, 19 Jun 2010 15:02:38 GMT</pubDate>
            <content:encoded><![CDATA[<p>Writing a library is a balancing act between the (sometimes competing) interests of API clarity, code clarity, performance and compressibility. In this article I'm going to detail three of the approaches we take to meet this balance and suggest them for your own usage.</p>
<h3>1. Collecting var statements</h3>
<p>Every time we declare variables we add 4 bytes to the compressed file size. Variables are declared sufficiently often that this can really add up, so instead of this:</p>
<pre><code class="language-javascript">var myFirstVar = 'something'; 
var myOtherVar = 'another thing'; 
var answer = 42; 
var adama = true;
</code></pre>
<p>One should use this form:</p>
<pre><code class="language-javascript">var myFirstVar = 'something', 
    myOtherVar = 'another thing', 
    answer = 42, 
    adama = true;
</code></pre>
<p>When this code is compressed, each variable name above is turned into a single-letter name, meaning that the wasted 4 bytes per useless additional 'var ' in the first example would have contributed significantly to code size with no benefit.</p>
<h3>2. Local variable pointers to object properties</h3>
<p>The following code (pruned from a previous version of Ext JS) is not as compressible as it could be:</p>
<pre><code class="language-javascript">var cs = {}; 
for (var n in this.modified) { 
    if (this.modified.hasOwnProperty(n)) { 
        cs[n] = this.data[n]; 
    } 
} 
return cs;  
</code></pre>
<p>We're better off aliasing 'this.modified' to a local variable first. Aside from the performance benefits some JS engines derive from not having to perform object property lookups over and over again, we save precious bytes this way too:</p>
<pre><code class="language-javascript">var modified = this.modified, 
    changes  = {}, 
    field; 

for (field in modified) { 
    if (modified.hasOwnProperty(field)) { 
        changes[field] = this.data[field]; 
    } 
}

return changes;
</code></pre>
<p>Again, the minifier will compress those variable names down to a single character each, so for our 'this.modified' example we're going to use 15 bytes to define the variable plus 1 byte each time we use it (totalling 15), vs the 26 bytes for that code previously. This approach scales especially well - were we to refer to this.modified a third time in the function, as our code will now minify to 16 bytes, vs 39 without the variable declaration.</p>
<p>Side note: in the first example here the variable 'n' was being used in the for...in loop. We can always safely exchange that for a meaningful variable name (in this case 'field') and leave the rest to the minifier.</p>
<h3>3. Aliasing 'this' to 'me'</h3>
<p>In Ext.data.Record's markDirty method we have the following code:</p>
<pre><code class="language-javascript">this.dirty = true; 
if(!this.modified){ 
    this.modified = {}; 
} 
this.fields.each(function(f) { 
    this.modified[f.name] = this.data[f.name]; 
},this);
</code></pre>
<p>There are 7 references to 'this' in that method, taking 28 bytes and completely incompressible. We could rewrite it like this (note that the final 'this' can be removed in this format):</p>
<pre><code class="language-javascript">var me = this; 

me.dirty = true; 
if (!me.modified) { 
    me.modified = {}; 
} 
me.fields.each(function(f) { 
    me.modified[f.name] = me.data[f.name]; 
});
</code></pre>
<p>Again, our minifier will change the 'me' var to a single character, saving us 8 bytes in this instance. That might not sound like a lot but after minification it equates to a 7% reduction in code size for this function.</p>
<p>In each of the cases above we're generally talking about single-digit percentage savings after minification. There is value in that small slice though, especially as more and more applications shift onto bandwidth-constrained mobile platforms.</p>
<p>The first two approaches are no-brainers and must always be done but the third is slightly more controversial. Personally I find it makes the code a little harder to read, largely because my syntax highlighter doesn't recognise that 'me' is now the same as 'this'. Its value also varies significantly by function - some functions can contain over a dozen references to 'this', in which case this approach makes a big difference.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext JS 3.2 beta out today]]></title>
            <link>https://edspencer.net//2010/3/9/ext-js-3-2-beta-out-today</link>
            <guid>ext-js-3-2-beta-out-today</guid>
            <pubDate>Tue, 09 Mar 2010 11:19:24 GMT</pubDate>
            <content:encoded><![CDATA[<p>We pushed out a <a href="http://www.extjs.com/blog/2010/03/09/announcing-ext-js-3-2-beta-multisort-transitions-and-composite-fields/">beta release of Ext JS 3.2</a> this morning. Although we've marked it as beta, it's a pretty solid release and we expect to release a final version shortly. The <a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/view/multisort-dataview.html">DataView transitions are especially fun</a> - watch this space for a fuller example...</p>
<p>Here's a quick rundown of the features we added:</p>
<ul>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/grid/multiple-sorting.html">Multiple sorting and filtering on Stores</a></li>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/view/multisort-dataview.html">Animated DataView transitions</a></li>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/form/composite-field.html">Composite Fields</a></li>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/slider/slider.html">Multiple handles on Sliders</a></li>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/slider/slider-field.html">Sliders in forms</a></li>
<li><a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/toolbar/reorderable.html">A couple</a> of <a href="http://www.extjs.com/deploy/ext-3.2-beta/examples/toolbar/droppable.html">Toolbar plugins</a></li>
<li><a href="http://www.extjs.com/deploy/dev/examples/form/form-grid-access.html">A new, more accessible theme</a></li>
</ul>
<p>One of the big projects we've undertaken that most people probably won't find so exciting is ramping up our internal QA efforts. Our unit test coverage has increased dramatically in the past couple of months, and we've built infrastructure to run all of our tests on every browser/OS we support in a fully automated fashion. Doing TDD on Ext JS is an awesome feeling.</p>
<p>I'll talk more in the future about what we're doing internally to ensure the quality of our code, framework performance and rendering.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Answering Nicholas Zakas' JavaScript quiz]]></title>
            <link>https://edspencer.net//2010/2/16/answering-nicholas-zakas-javascript-quiz</link>
            <guid>answering-nicholas-zakas-javascript-quiz</guid>
            <pubDate>Tue, 16 Feb 2010 12:41:17 GMT</pubDate>
            <content:encoded><![CDATA[<p>A current meme that's floating around the JavaScript geek corner of the internet is setting quizzes on some of the more unusual aspects of JavaScript. This time round <a href="http://www.nczonline.net/blog/2010/02/16/my-javascript-quiz/">Nicholas Zakas is providing the entertainment</a>, so I thought I'd provide some answers. Let's get started:</p>
<h2>Question 1</h2>
<p>Question 1 looks like this:</p>
<pre><code class="language-javascript">
var num1 = 5,
    num2 = 10,
    result = num1+++num2;
</code></pre>
<p>We're asked what the values of result, num2 and num1 are. First, let's deconstruct what that +++ is doing. There is no +++ operator in JavaScript - instead we have a num1++ followed by a + num2.</p>
<p>JavaScript has two ways of incrementing a number by 1 - we can either put the ++ before the variable or after it. The variable is incremented either way - the only difference is what is returned. ++10 returns 11, whereas 10++ returns 10:</p>
<pre><code class="language-javascript">
var a = 10;

var b = a++; //a is set to 11 now, but b is set to 10
var c = ++a; //a is set to 12 now, c is also set to 12
</code></pre>
<p>So 'result' is the sum of num1++ (which is 5) and num2, which is 10, so result equals 15. num2 remains at 10 as it was not modified. num1 is now equal to 6 because we incremented it by 1, though the incrementation did not affect the sum passed to result.</p>
<h2>Question 2</h2>
<pre><code class="language-javascript">
var x = 5,
    o = {
        x: 10,
        doIt: function doIt(){
            var x = 20;
            setTimeout(function(){
                alert(this.x);
            }, 10);
        }
    };
o.doIt();
</code></pre>
<p>We're asked what is alerted. This is mostly smoke and mirrors - there's some indirection with all the duplicate names but the important thing here is the setTimeout. The function we pass to setTimeout gets run in the global scope, meaning 'this' refers to the window object. Declaring x as a variable in the global scope (var x = 5) is the same as setting window.x = 5, so 5 is alerted.</p>
<h2>Question 3</h2>
<pre><code class="language-javascript">
var num1 = "10",
    num2 = "9";
</code></pre>
<p>We're asked:</p>
<ul>
<li>What is the value of num1 &#x3C; num2?</li>
<li>What is the value of +num1 &#x3C; num2?</li>
<li>What is the value of num1 + num2?</li>
<li>What is the value of +num1 + num2?</li>
</ul>
<p>This question is all about type casting.</p>
<ul>
<li>num1 &#x3C; num2 is true - if both operands are strings JavaScript will compare them alphabetically, and "10" is lower alphabetically than "9"</li>
<li>+num1 &#x3C; num2 is false - placing a "+" operator before a string casts it into a number, so we're actually testing 10 &#x3C; "9". When testing a mixture of numbers and strings like this, everything is cast into a number, so we're testing 10 &#x3C; 9, which is false</li>
<li>num1 + num2 === "109" - the plus sign can mean both addition and concatenation, depending on the operand types. Here we have 2 strings so we're concatenating them together</li>
<li>+num1 + num2 === "109" also - again we're casting num1 into a number, but the + operator means concatenation if at least one operand is a string</li>
</ul>
<p>The confusion around this comes largely from the fact that the plus sign is used for both addition and concatenation in JavaScript. This causes the engine to have to test the typeof each operand and cast accordingly. All of the other math operators (e.g. /, *, % etc) cast both operands to numbers.</p>
<h2>Question 4</h2>
<pre><code class="language-javascript">
var message = "Hello world!";
</code></pre>
<p>We're asked:</p>
<ul>
<li>What is the value of message.substring(1, 4)?</li>
<li>What is the value of message.substr(1,4)?</li>
</ul>
<p>substring and substr do similar things. The first argument to each is the character index to start from, but whereas substring's second argument is the character index to end at, substr's second argument is the number of characters to return.  Therefore message.substr(1, 4) will return a string of length 4, whereas message.substring(1, 4) will return a string of length 3 (4 - 1):</p>
<ul>
<li>message.substring(1, 4); //"ell"</li>
<li>message.substr(1, 4); //"ello"</li>
</ul>
<h2>Question 5</h2>
<pre><code class="language-javascript">
var o = {
        x: 8,

        valueOf: function(){
            return this.x + 2;
        },
        toString: function(){
            return this.x.toString();
        }
    },
    result = o &#x3C; "9";
alert(o);
</code></pre>
<p>We're asked the value of 'result', and what gets alerted. This requires an understanding of the special valueOf and toString functions available on every object. These functions are used internally by the JavaScript engine to pull out the best representation of an object's value based on the situation.</p>
<p>When alerting a value, we want a string representation so toString is called. When comparing the value to another object, valueOf is called instead. So alert(o) alerts "8", and result is set equal to the result of 10 &#x3C; "9". The JavaScript engine will decide when to use which option, or we can specify it ourselves:</p>
<pre><code class="language-javascript">var num1 = 8, num2 = 9;

num1 + num2; //17
num1.toString() + num2.toString(); //"89"
num1.valueOf() + num2.valueOf(); //17
</code></pre>
<p>The 'result' assignment needs a little explanation. First, the engine calls valueOf on the object, which returns 10. Second, because one of the operands to the &#x3C; operator is a number, the other is also cast into a number, so we are testing 10 &#x3C; 9, which returns false. We could instead force it to use toString: o.toString() &#x3C; "9" returns true.</p>
<p>Quizzes like this are great for getting your teeth into some of the guts of JavaScript, but don't mistake them for a good way to write code. The point is to demonstrate how quirky JS code can be unless you write it in a sensible way.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaml updates]]></title>
            <link>https://edspencer.net//2010/1/29/jaml-updates</link>
            <guid>jaml-updates</guid>
            <pubDate>Fri, 29 Jan 2010 01:56:43 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="http://github.com/edspencer/jaml">Jaml</a> seems to have been getting a lot of interest lately. Here are a few quick updates on what's been going on:</p>
<ul>
<li><a href="http://github.com/edspencer/jaml/commit/69e82e1179f830cf221b0ce1a48c8ea3f23fcc43">Tom Robinson added support</a> for <a href="http://commonjs.org/">CommonJS</a></li>
<li>Eneko Alonso <a href="http://dev.enekoalonso.com/2010/01/17/mooml-mootools-markup-language-intro/">ported the project to MooTools, creating mooml</a></li>
<li>Carl Furrow wrote up a <a href="http://carlfurrow.com/2010/01/javascript-html-templating-ejs-vs-jaml/">nice comparison on Jaml and EJS</a></li>
<li><a href="http://twitter.com/javascriptmvc/status/8312859889">Jaml is now a rendering option in JavaScriptMVC</a>, along with <a href="http://ejohn.org/blog/javascript-micro-templating/">John Resig's microtemplates</a></li>
<li>Andrew Dupont committed a series of patches such as <a href="http://github.com/edspencer/jaml/commit/db6ab758abe489e60a9623e1cc466ada5288873b">improving Jaml's efficiency</a> and <a href="http://github.com/edspencer/jaml/commit/583f998844d06875d20d1f08d9ca74822ba419e4">optionally removing the 'with' and 'eval' magic</a></li>
</ul>
<p>In addition <a href="http://ajaxian.com/archives/jaml-an-html-builder-a-la-haml">Jaml was recently picked up by Ajaxian</a>, and <a href="http://www.omeyasweb.com/generar-html-desde-javascript/">a couple</a> <a href="http://www.anieto2k.com/2009/12/07/jaml-bonita-forma-de-generar-html-desde-javascript/">of people</a> have written up blog posts about Jaml in languages other than English, which is great to see.</p>
<p><a href="http://github.com/edspencer/jaml">Jaml is up on Github</a> and has a number of forks already. If you like the library and have something to add, fork away and send me a pull request!</p>
<p>If you've never seen Jaml before or have forgotten what it does, it turns this:</p>
<pre><code class="language-javascript">div(
  h1("Some title"),
  p("Some exciting paragraph text"),
  br(),

  ul(
    li("First item"),
    li("Second item"),
    li("Third item")
  )
);
</code></pre>
<p>Into this:</p>
<pre><code class="language-html">&#x3C;div>
  &#x3C;h1>Some title&#x3C;/h1>
  &#x3C;p>Some exciting paragraph text&#x3C;/p>
  &#x3C;br />
  &#x3C;ul>
    &#x3C;li>First item&#x3C;/li>
    &#x3C;li>Second item&#x3C;/li>
    &#x3C;li>Third item&#x3C;/li>
  &#x3C;/ul>
&#x3C;/div>
</code></pre>
<p><a href="http://edspencer.net/2009/11/jaml-beautiful-html-generation-for-javascript.html">See the original post for more details.</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext JS is looking for a QA rockstar]]></title>
            <link>https://edspencer.net//2010/1/19/ext-js-is-looking-for-a-qa-rockstar</link>
            <guid>ext-js-is-looking-for-a-qa-rockstar</guid>
            <pubDate>Tue, 19 Jan 2010 00:05:18 GMT</pubDate>
            <content:encoded><![CDATA[<p>This has been <a href="http://www.extjs.com/forum/showthread.php?t=90018">cross-posted from our Open Discussion Forum</a>.</p>
<p>As part of our ambition of creating the world's best JavaScript framework, we're looking to hire a special somebody to help maintain the high quality of our components.</p>
<p>While we have one eye on implementing new features and improving Ext JS's performance, the other is on making sure what we already have still works well.</p>
<p>This is a difficult job and we need someone smart, focused and well versed in Ext JS.  Somebody who will:</p>
<ul>
<li>Use our existing systems to test components as new builds of the library are landed</li>
<li>Maintain a strong presence in the forums and be the first to know of any reported issues</li>
<li>Respond to bug tickets such as rendering issues and broken functionality</li>
<li>Totally own the Quality Assurance of Ext JS - we want your ideas and your initiative as well as your expertise with Ext</li>
<li>Liaise with the core team on a daily basis</li>
</ul>
<p>This is a full-time position, though allowances can be made for the right person.  If you think you would enjoy working with Ext JS, and have what it takes to help us keep Ext at the forefront of our field, <a href="http://www.extjs.com/forum/private.php?do=newpm&#x26;u=59955">drop me a private message</a> with the following information:</p>
<ul>
<li>Your name</li>
<li>Email address</li>
<li>Location (city, country, timezone)</li>
<li>All experience with Ext JS</li>
<li>Bonus points for links to open source software</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2010: The year Ext JS takes over]]></title>
            <link>https://edspencer.net//2010/1/13/2010-the-year-ext-js-takes-over</link>
            <guid>2010-the-year-ext-js-takes-over</guid>
            <pubDate>Wed, 13 Jan 2010 09:12:07 GMT</pubDate>
            <content:encoded><![CDATA[<p>On January 1st 2010 I officially joined <a href="http://extjs.com">Ext JS</a> to take over the role of lead developer.  <a href="http://edspencer.net/category/extjs">After living and breathing Ext for the last 3 years</a> I am delighted to have joined the company itself.  Ext JS has lead the way in developing rich client side applications since the very first release; this is a tradition we will continue and build upon.</p>
<p>2010 is going to be an extremely exciting year for Ext JS.  A new focus is being placed on helping developers create their applications much more quickly, with the help of <a href="http://www.extjs.com/blog/2009/10/08/ext-js-designer-preview/">advanced creation tools</a> and a standardised application architecture right out of the box.</p>
<p>We will continue the <a href="http://www.extjs.com/blog/2009/12/17/ext-js-3-1-massive-memory-improvements-treegrid-and-more%e2%80%a6/">performance improvements started in 3.1</a> to make sure that Ext applications really fly.  Ext JS 3.2 will be the fastest, most stable version ever released.</p>
<p>2010 is also the year that Ext JS becomes much easier to learn.  With a completely reinvented learning section, Ext will no longer take months to learn and understand - even our API documentation will get a facelift.</p>
<p>The upcoming Marketplace will be the perfect venue to find and share new, high quality components created by our awesome developer community.  Think of the Marketplace as the App Store for Ext JS - full of great offerings that are easy to drop in to any application.</p>
<h2>Calling all able-minded Ext JS developers</h2>
<p>Ext JS is already the best JavaScript library in the world for creating rich, desktop-quality applications on the web.  If you want to help us make it even better, I want to hear from you.</p>
<p>As well as creating new components and improving our application support, we need people to help us maintain the quality and stability of what we already have.  If you're intimate with Ext and think you have what it takes to get involved, <a href="http://www.extjs.com/forum/member.php?u=59955">drop me a PM and introduce yourself</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OSX Screensaver emulation with Canvas: That's Bean]]></title>
            <link>https://edspencer.net//2009/12/6/osx-screensaver-emulation-with-canvas-thats-bean</link>
            <guid>osx-screensaver-emulation-with-canvas-thats-bean</guid>
            <pubDate>Sun, 06 Dec 2009 18:50:31 GMT</pubDate>
            <content:encoded><![CDATA[<p>OS X has a pretty little screensaver which takes a bunch of images and 'drops' them, spinning, onto the screen.  Think of it like scattering photographs onto a table, one at a time.</p>
<p>Naturally, there's a desperate need for a JavaScript/Canvas port of this functionality, resulting in the following:</p>
<p>I had to limit the video capture framerate a bit so the video makes it look less smooth than it actually is.  <a href="http://code.edspencer.net/Bean/index.html">Check it out running in your own browser here</a>.</p>
<p>For obvious reasons I have called the code behind this Bean, and it's all available <a href="http://github.com/edspencer/Bean">up on Github</a>.</p>
<p>For the curious, here's a little explanation about how it works.  Bean starts off with a blank canvas and a list of image urls, which it preloads before getting started.  It then drops one image at a time, rotating it as it goes.  Each falling image is called a Plunger, because it <strong>plunges</strong>.</p>
<p>Each Plunger gets a random position and rotation to end up in, and takes care of drawing itself to the canvas on each frame by calculating its current size and rotation as it falls away from you.</p>
<p>Drawing each Plunger image on every frame quickly starts to kill the CPU, so we take a frame snapshot every time a Plunger has finished its descent.  This just entails drawing the completed Plunges first and then using Canvas' getImageData API to grab the pixel data for the image.</p>
<p>This gives us a snapshot of all of the fallen Plungers, meaning we can just draw a single background image and the currently falling Plunger on each frame.  This approach ensures the performance remains constant, as we are only ever drawing a maximum of 2 images per frame.  Each time a Plunger finishes its descent a new snapshot is taken.</p>
<p>Bean attempts to draw a new frame roughly 25 times per second and modern browsers seem to handle this pretty well.  Safari pulls around 60% of one core on my MacBook Pro, with Firefox somewhat less performant.  Needless to say, I didn't even bother trying to make this work with IE.</p>
<p>Here's the code to set the Bean in motion.  This is using a few bundled <a href="http://apod.nasa.gov/apod/">APOD images</a>:</p>
<pre><code class="language-javascript">var bean = new Bean({
  imageUrls: [
    'images/DoubleCluster_cs_fleming.jpg',
    'images/NGC660Hagar0_c900.jpg',
    'images/filaments_iac.jpg',
    'images/m78wide_tvdavis900.jpg',
    'images/sunearthpanel_sts129.jpg',
    'images/NGC253_SSRO_900.jpg',
    'images/Ophcloud_spitzer_c800.jpg'
  ],
  canvasId : 'main',
  fillBody : true
});

bean.onReady(function(bean) {
  bean.start();
});
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext.ux.Exporter - export any Grid to Excel or CSV]]></title>
            <link>https://edspencer.net//2009/11/24/ext-ux-exporter-export-any-grid-to-excel-or-csv</link>
            <guid>ext-ux-exporter-export-any-grid-to-excel-or-csv</guid>
            <pubDate>Tue, 24 Nov 2009 09:32:33 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sometimes we want to print things, like grids or trees.  The <a href="http://edspencer.net/2009/07/extuxprinter-printing-for-any-ext.html">Ext JS printing plugin</a> is pretty good for that.  But what if we want to export them instead? Enter <a href="http://github.com/edspencer/Ext.ux.Exporter">Ext.ux.Exporter</a>.</p>
<p>Ext.ux.Exporter allows any store-based component (such as grids) to be exported, <strong>locally</strong>, to Excel or any other format.  It does not require any server side programming - the export document is generated on the fly, entirely in JavaScript.</p>
<p>The extension serves as a base for exporting any kind of data, but comes bundled with a .xls export formatter suitable for exporting any Grid straight to Excel.  Here's how to do that:</p>
<pre><code class="language-javascript">var grid = new Ext.grid.GridPanel({
  store: someStore,
  tbar : [
    {
      xtype: 'exportbutton',
      store: someStore
    }
  ],
  //your normal grid config goes here
});
</code></pre>
<p>Clicking the Download button in the top toolbar iterates over the data in the store and creates an Excel file locally, before Base64 encoding it and redirecting the browser via a data url.  If you have Excel or a similar program installed your browser should ask you to save the file or open it with Excel.</p>
<p>I put together a quick example of the plugin in action inside the repository, just <a href="http://github.com/edspencer/Ext.ux.Exporter">clone</a> or <a href="http://github.com/edspencer/Ext.ux.Exporter/zipball/master">download the code</a> and drag the examples/index.html file into your browser to run it.</p>
<p>The Exporter will work with any store or store-based component.  It also allows export to any format - for example CSV or PDF.  Although the Excel Formatter is probably the most useful, implementing a CSV or other Formatter should be trivial - check out the Excel Formatter example in the <a href="http://github.com/edspencer/Ext.ux.Exporter/tree/master/ExcelFormatter/">ExcelFormatter</a> directory.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaml: beautiful HTML generation for JavaScript]]></title>
            <link>https://edspencer.net//2009/11/4/jaml-beautiful-html-generation-for-javascript</link>
            <guid>jaml-beautiful-html-generation-for-javascript</guid>
            <pubDate>Wed, 04 Nov 2009 12:03:15 GMT</pubDate>
            <content:encoded><![CDATA[<p>Generating HTML with JavaScript has always been ugly.  Hella ugly.  It usually involves writing streams of hard-to-maintain code which just concatenates a bunch of strings together and spits them out in an ugly mess.</p>
<p>Wouldn't it be awesome if we could do something pretty like this:</p>
<pre><code class="language-javascript">div(
  h1("Some title"),
  p("Some exciting paragraph text"),
  br(),

  ul(
    li("First item"),
    li("Second item"),
    li("Third item")
  )
);
</code></pre>
<p>And have it output something beautiful like this:</p>
<pre><code class="language-html">&#x3C;div>
  &#x3C;h1>Some title&#x3C;/h1>
  &#x3C;p>Some exciting paragraph text&#x3C;/p>
  &#x3C;br />
  &#x3C;ul>
    &#x3C;li>First item&#x3C;/li>
    &#x3C;li>Second item&#x3C;/li>
    &#x3C;li>Third item&#x3C;/li>
  &#x3C;/ul>
&#x3C;/div>
</code></pre>
<p>With <a href="http://github.com/edspencer/jaml">Jaml</a>, we can do exactly that.  Jaml is a simple library inspired by the excellent <a href="http://haml-lang.com">Haml library</a> for Ruby.  It works by first defining a template using an intuitive set of tag functions, and then rendering it to appear as pretty HTML.  Here's an example of how we'd do that with the template above:</p>
<pre><code class="language-javascript">Jaml.register('simple', function() {
  div(
    h1("Some title"),
    p("Some exciting paragraph text"),
    br(),

    ul(
      li("First item"),
      li("Second item"),
      li("Third item")
    )
  );
});

Jaml.render('simple');
</code></pre>
<p>All we need to do is call Jaml.register with a template name and the template source.  Jaml then stores this for later use, allowing us to render it later using Jaml.render().  Rendering with Jaml gives us the nicely formatted, indented HTML displayed above.</p>
<p>So we've got a nice way of specifying reusable templates and then rendering them prettily, but we can do more.  Usually we want to inject some data into our template before rendering it - like this:</p>
<pre><code class="language-javascript">Jaml.register('product', function(product) {
  div({cls: 'product'},
    h1(product.title),

    p(product.description),

    img({src: product.thumbUrl}),
    a({href: product.imageUrl}, 'View larger image'),

    form(
      label({'for': 'quantity'}, "Quantity"),
      input({type: 'text', name: 'quantity', id: 'quantity', value: 1}),

      input({type: 'submit', value: 'Add to Cart'})
    )
  );
});
</code></pre>
<p>In this example our template takes an argument, which we've called product.  We could have called this anything, but in this case the template is for a product in an ecommerce store so product makes sense.  Inside our template we have access to the product variable, and can output data from it.</p>
<p>Let's render it with a Product from our database:</p>
<pre><code class="language-javascript">//this is the product we will be rendering
var bsg = {
  title      : 'Battlestar Galactica DVDs',
  thumbUrl   : 'thumbnail.png',
  imageUrl   : 'image.png',
  description: 'Best. Show. Evar.'
};

Jaml.render('product', bsg);
</code></pre>
<p>The output from rendering this template with the product looks like this:</p>
<pre><code class="language-html">&#x3C;div class="product">
  &#x3C;h1>Battlestar Galactica DVDs&#x3C;/h1>
  &#x3C;p>Best. Show. Evar.&#x3C;/p>
  &#x3C;img src="thumbnail.png" />
  &#x3C;a href="image.png">View larger image&#x3C;/a>
  &#x3C;form>
    &#x3C;label for="quantity">Quantity&#x3C;/label>
    &#x3C;input type="text" name="quantity" id="quantity" value="1">&#x3C;/input>
    &#x3C;input type="submit" value="Add to Cart">&#x3C;/input>
  &#x3C;/form>
&#x3C;/div>
</code></pre>
<p>Cool - we've got an object oriented declaration of an HTML template which is cleanly separated from our data.  How about we define another template, this time for a category which will contain our products:</p>
<pre><code class="language-javascript">Jaml.register('category', function(category) {
  div({cls: 'category'},
    h1(category.name),
    p(category.products.length + " products in this category:"),

    div({cls: 'products'},
      Jaml.render('product', category.products)
    )
  );
});
</code></pre>
<p>Our category template references our product template, achieving something rather like a <a href="http://addictedtonew.com/archives/149/a-bit-on-rails-partials/">partial</a> in Ruby on Rails.  This obviously allows us to keep our templates <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> and to easily render a hypothetical Category page like this:</p>
<pre><code class="language-javascript">//here's a second product
var snowWhite = {
  title      : 'Snow White',
  description: 'not so great actually',
  thumbUrl   : 'thumbnail.png',
  imageUrl   : 'image.png'
};

//and a category
var category = {
  name    : 'Doovde',
  products: [bsg, snowWhite]
}

Jaml.render('category', category);
</code></pre>
<p>All we've done is render the 'category' template with our 'Doovde' category, which contains an array of products.  These were passed into the 'product' template to produce the following output:</p>
<pre><code class="language-html">&#x3C;div class="category">
  &#x3C;h1>Doovde&#x3C;/h1>
  &#x3C;p>2 products in this category:&#x3C;/p>
  &#x3C;div class="products">&#x3C;div class="product">
  &#x3C;h1>Battlestar Galactica DVDs&#x3C;/h1>
  &#x3C;p>Best. Show. Evar.&#x3C;/p>
  &#x3C;img src="thumbnail.png" />
  &#x3C;a href="image.png">View larger image&#x3C;/a>
  &#x3C;form>
    &#x3C;label for="quantity">Quantity&#x3C;/label>
    &#x3C;input type="text" name="quantity" id="quantity" value="1">&#x3C;/input>
    &#x3C;input type="submit" value="Add to Cart">&#x3C;/input>
  &#x3C;/form>
&#x3C;/div>
&#x3C;div class="product">
  &#x3C;h1>Snow White&#x3C;/h1>
  &#x3C;p>not so great actually&#x3C;/p>
  &#x3C;img src="thumbnail.png" />
  &#x3C;a href="image.png">View larger image&#x3C;/a>
  &#x3C;form>
    &#x3C;label for="quantity">Quantity&#x3C;/label>
    &#x3C;input type="text" name="quantity" id="quantity" value="1">&#x3C;/input>
    &#x3C;input type="submit" value="Add to Cart">&#x3C;/input>
  &#x3C;/form>
&#x3C;/div>
&#x3C;/div>
&#x3C;/div>
</code></pre>
<p>You can see live examples of all of the above at <a href="http://edspencer.github.com/jaml">http://edspencer.github.com/jaml</a>.</p>
<p>Jaml currently sports a few hacks and is not particularly efficient.  It is presented as a proof of concept, though all the output above is true output from the library.  As always, all of the code is up on <a href="http://github.com/edspencer/jaml">Github</a>, and contributions are welcome :)</p>
<p>Jaml would be suitable for emulating a Rails-style directory structure inside a server side JavaScript framework - each Jaml template could occupy its own file, with the template name coming from the file name.  This is roughly how Rails and other MVC frameworks work currently, and it eliminates the need for the Jaml.register lines.  Alternatively, the templates could still be stored server side and simply pulled down and evaluated for client side rendering.</p>
<p>Happy rendering!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Making RowEditor use your column renderers]]></title>
            <link>https://edspencer.net//2009/10/29/making-roweditor-use-your-column-renderers</link>
            <guid>making-roweditor-use-your-column-renderers</guid>
            <pubDate>Thu, 29 Oct 2009 09:42:47 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="http://edspencer.net/2009/09/using-the-extjs-row-editor.html">The RowEditor plugin is one of my favourite Ext JS components</a>.  It basically allows any row on a grid to be turned into an adhoc form on the fly, saving you the effort of defining additional form components.</p>
<p>Recently I had a grid which had a few fields that don't have an editor, something like this:</p>
<pre><code class="language-javascript">var myGrid = new Ext.grid.GridPanel({
  plugins: [new Ext.ux.grid.RowEditor()],
  columns: [
    {
      header   : "Username",
      dataIndex: 'username',
      editor   : new Ext.form.TextField()
    },
    {
      header   : "Signup date",
      dataIndex: 'created_at',
      renderer : Ext.util.Format.dateRenderer('m/d/Y')
    }
  ]
});
</code></pre>
<p>Simple stuff - we just show a username and a signup date, which is altered by a renderer.  When we double-click a row it turns into an editable row, and we get a textfield allowing us to edit the username.  Unfortunately, while in edit mode our date renderer is ignored, and the raw value displayed instead.</p>
<p>Thankfully, we can fix this by altering <a href="http://www.extjs.com/deploy/dev/examples/ux/RowEditor.js">RowEditor's source code</a>.  The method we need to change is startEditing, which sadly suffers from <a href="http://edspencer.net/2009/10/writing-better-javascript-split-up-long-methods.html">long method syndrome</a>.  About halfway into that method there's a for loop, which we're going to alter to look like this:</p>
<pre><code class="language-javascript">for (var i = 0, len = cm.getColumnCount(); i &#x3C; len; i++){
  val = this.preEditValue(record, cm.getDataIndex(i));
  f = fields[i];
  
  //our changes start here
  var column = cm.getColumnById(cm.getColumnId(i));
  
  val = column.renderer.call(column, val, {}, record);
  //our changes end here
  
  f.setValue(val);
  this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
</code></pre>
<p>We didn't really have to do much, just grab the renderer for the column and pass it the default value and the record which was found earlier in the method.</p>
<p>For the curious, the empty object we pass in as the second argument to the renderer is what would usually be the 'meta' object (<a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.grid.Column">see the renderer documentation on the Column class</a>).  Under the covers, RowEditor actually creates an Ext.form.DisplayField instance for each column that you don't specify an editor for.  This is why we use f.setValue(val); above.  DisplayField doesn't have the same meta stuff as a normal cell would, so if you're looking to customise CSS via the metadata you'll have to do something like this instead:</p>
<pre><code class="language-javascript">columns: [
  {
     ...
    editor: new Ext.form.DisplayField({
      cls: 'myCustomCSSClass',
      style: 'border: 10px solid red;'
    })
  }
]
</code></pre>
<p>Pretty easy.  It's a shame we have to overwrite the source code as this makes the solution less future proof, but if you look at <a href="http://www.extjs.com/deploy/dev/examples/ux/RowEditor.js">RowEditor's source code</a> you'll see why a 45 line override would be equally unpleasant.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[git: what to do if you commit to no branch]]></title>
            <link>https://edspencer.net//2009/10/28/git-what-to-do-if-you-commit-to-no-branch</link>
            <guid>git-what-to-do-if-you-commit-to-no-branch</guid>
            <pubDate>Wed, 28 Oct 2009 17:14:56 GMT</pubDate>
            <content:encoded><![CDATA[<p>Using git, you'll sometimes find that you're not on any branch.  This usually happens when you're using a submodule inside another project.  Sometimes you'll make some changes to this submodule, commit them and then try to push them up to a remote repository:</p>
<pre><code class="language-bash">ed$ git commit -m "My excellent commit"
[detached HEAD d2bdb98] My excellent commit
 3 files changed, 3 insertions(+), 3 deletions(-)
ed$ git push origin master
Everything up-to-date
</code></pre>
<p>Er, what?  Everything is <strong>not</strong> up to date - I just made changes!  The clue is in the first part of the commit response - [detached HEAD d2bdb98].  This just means that we've made a commit without actually being on any branch.</p>
<p>Luckily, this is easy to solve - all we need to do is checkout the branch we should have been on and merge in that commit SHA:</p>
<pre><code class="language-bash">ed$ git checkout master
Previous HEAD position was d2bdb98... My excellent commit
Switched to branch 'master'
ed$ git merge d2bdb98
Updating 88f218b..d2bdb98
Fast forward
 ext-mvc-all-min.js |    2 +-
 ext-mvc-all.js     |    2 +-
 view/FormWindow.js |    2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)
</code></pre>
<p>Once we got onto the master branch, we just called git merge with the SHA reference for the commit we just made (d2bd98), which applied our commit to the master branch.  The output tells us that the commit was applied, and now we can push up to our remote repository as normal:</p>
<pre><code class="language-bash">ed$ git push origin master
Counting objects: 11, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 692 bytes, done.
Total 6 (delta 4), reused 0 (delta 0)
To git@github.com:extmvc/extmvc.git
   88f218b..d2bdb98  master -> master
</code></pre>
<p>This had me puzzled for a while so hopefully it'll save someone banging their head against a nearby wall.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Better JavaScript - split up long methods]]></title>
            <link>https://edspencer.net//2009/10/6/writing-better-javascript-split-up-long-methods</link>
            <guid>writing-better-javascript-split-up-long-methods</guid>
            <pubDate>Tue, 06 Oct 2009 15:51:51 GMT</pubDate>
            <content:encoded><![CDATA[<p>For the second time this week I'm going to pick on the usually delightful <a href="http://extjs.com">Ext JS</a> library. Last time we discussed <a href="http://edspencer.net/2009/10/javascript-module-pattern-overused-dangerous-and-bloody-annoying.html">the overzealous use of the Module pattern</a>; this time it's the turn of bloated methods.</p>
<p>As before, I'm not really picking on Ext at all - this happens all over the place. But again, this is the library closest to my heart and the one I know the best.</p>
<h2>The Problem</h2>
<p>We're going to take a look at <a href="http://www.extjs.com/deploy/dev/docs/source/XmlReader.html#method-Ext.data.XmlReader-readRecords">Ext.data.XmlReader's readRecords method</a>.  Before we get started though, I'll repeat that this is intended as an example of an approach, not a whine at Ext in particular.</p>
<pre><code class="language-javascript">/**
 * Create a data block containing Ext.data.Records from an XML document.
 * @param {Object} doc A parsed XML document.
 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
 * a cache of Ext.data.Records.
 */
readRecords: function(doc) {
  /**
   * After any data loads/reads, the raw XML Document is available for further custom processing.
   * @type XMLDocument
   */
  this.xmlData = doc;
  var root = doc.documentElement || doc;
  var q = Ext.DomQuery;
  var recordType = this.recordType, fields = recordType.prototype.fields;
  var sid = this.meta.idPath || this.meta.id;
  var totalRecords = 0, success = true;
  if(this.meta.totalRecords){
    totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
  }

  if(this.meta.success){
    var sv = q.selectValue(this.meta.success, root, true);
    success = sv !== false &#x26;&#x26; sv !== 'false';
  }
  var records = [];
  var ns = q.select(this.meta.record, root);
  for(var i = 0, len = ns.length; i &#x3C; len; i++) {
    var n = ns[i];
    var values = {};
    var id = sid ? q.selectValue(sid, n) : undefined;
    for(var j = 0, jlen = fields.length; j &#x3C; jlen; j++){
      var f = fields.items[j];
      var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue);
      v = f.convert(v, n);
      values[f.name] = v;
    }
    var record = new recordType(values, id);
    record.node = n;
    records[records.length] = record;
  }

  return {
    success : success,
    records : records,
    totalRecords : totalRecords || records.length
  };
}
</code></pre>
<p>Anyone care to tell me what this actually does? Personally, I have absolutely no idea. I recently found myself needing to implement an XmlReader subclass with a twist which required understanding how this works, and let's just say it wasn't easy!</p>
<p>So what is it that makes the above so terrifyingly hard to understand? Well, in no particular order:</p>
<ul>
<li>It's <strong>too long</strong> - you'd need to be a genius to easily understand what's going on here</li>
<li>The variable names <strong>don't make much sense</strong> - some of the oddest include 'q', 'ns', 'v', 'f' and 'sv'</li>
<li>There's <strong>minimal commenting</strong> - we're given a single-line clue at the very top as to what these 40-odd lines do</li>
</ul>
<h2>A Solution</h2>
<p>Let's see how the reworked code below addresses each of the concerns above:</p>
<ul>
<li>Although we end up with more lines of code here, no single method is more than around 10 LOC</li>
<li>No single letter variable names - you no longer have to decode what 'sv' means</li>
<li>Constructive commenting allows rapid comprehension by skimming the text</li>
</ul>
<p>One additional and enormous benefit here comes directly from splitting logic into discrete methods.  Previously if you'd wanted to implement your own logic to determine success, get the total number of records or even build a record from an XML node you'd be stuck.  There was no way to selectively override that logic without redefining that entire monster method.</p>
<p>With our new approach this becomes trivial:</p>
<pre><code class="language-javascript">Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
  readRecords: function(doc) {
    this.xmlData = doc;
    
    //get local references to frequently used variables
    var root    = doc.documentElement || doc,
        records = [],
        nodes   = Ext.DomQuery.select(this.meta.record, root);
    
    //build an Ext.data.Record instance for each node
    Ext.each(nodes, function(node) {
      records.push(this.buildRecordForNode(node));
    }, this);

    return {
      records     : records,
      success     : this.wasSuccessful(root),
      totalRecords: this.getTotalRecords(root) || records.length
    };
  },
  
  /**
   * Returns a new Ext.data.Record instance using data from a given XML node
   * @param {Element} node The XML node to extract Record values from
   * @return {Ext.data.Record} The record instance
   */
  buildRecordForNode: function(node) {
    var domQuery = Ext.DomQuery,
        idPath   = this.meta.idPath || this.meta.id,
        id       = idPath ? domQuery.selectValue(idPath, node) : undefined;
        
    var record  = new this.recordType({}, id);
    record.node = node;
    
    //iterate over each field in our record, find it in the XML node and convert it
    record.fields.each(function(field) {
      var mapping  = Ext.value(field.mapping, field.name, true),
          rawValue = domQuery.selectValue(mapping, node, field.defaultValue),
          value    = field.convert(rawValue, node);
      
      record.set(field.name, value);
    });
    
    return record;
  },
  
  /**
   * Returns the total number of records indicated by the server response
   * @param {XMLDocument} root The XML response root node
   * @return {Number} total records
   */
  getTotalRecords: function(root) {
    var metaTotal = this.meta.totalRecords;
    
    return metaTotal == undefined 
                      ? 0 
                      : Ext.DomQuery.selectNumber(metaTotal, root, 0);
  },
  
  /**
   * Returns true if the response document includes the expected success property
   * @param {XMLDocument} root The XML document root node
   * @return {Boolean} True if the XML response was successful
   */
  wasSuccessful: function(root) {
    var metaSuccess  = this.meta.success;
    
    //return true for any response except 'false'
    if (metaSuccess == undefined) {
      return true;
    } else {
      var successValue = Ext.DomQuery.selectValue(metaSuccess, root, true);
      return successValue !== false &#x26;&#x26; successValue !== 'false';
    }
  }
});
</code></pre>
<p>(For brevity I have omitted the existing readRecords comment blocks from the above)</p>
<p>I suggest that you structure your code in this way at least 99% of the time.  The one exception is if high performance is an issue.  If you are in a situation where every millisecond counts (you probably aren't), then taking the former route becomes more acceptable (though there's still no excuse for not adding a few comments explaining what the code actually does).</p>
<p>My refactored code almost certainly runs slower than the original as it doesn't take as much advantage of cached local variables as the monolithic version does.  For library-level code this can make sense if the performance gain is significant, but for the everyday code you and I write it is rarely a good idea.</p>
<p>I'll be watching.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Module pattern - overused, dangerous and bloody annoying]]></title>
            <link>https://edspencer.net//2009/10/5/javascript-module-pattern-overused-dangerous-and-bloody-annoying</link>
            <guid>javascript-module-pattern-overused-dangerous-and-bloody-annoying</guid>
            <pubDate>Mon, 05 Oct 2009 18:33:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>The <a href="http://yuiblog.com/blog/2007/06/12/module-pattern/">Module Pattern</a> is a way of using a <a href="http://james.padolsey.com/javascript/closures-in-javascript/">closure</a> in JavaScript to create private variables and functions.  Here's a brief recap:</p>
<pre><code class="language-javascript">var myObject = (function() {
  //these are only accessible internally
  var privateVar = 'this is private';
  var privateFunction = function() {
    return "this is also private";
  };
  
  return {
    //these can be accessed externally
    publicVar: 'this is public',
    
    publicFunction: function() {
      return "this is also public"
    },

    //this is a 'privileged' function - it can access the internal private vars
    myFunction: function() {
      return privateVar;
    }
  };
})();

myObject.privateVar; //returns null as private var is private
myObject.myFunction(); //return the private var as myFunction has access to private properties
</code></pre>
<p>Breaking this down, we create a function which is executed immediately (via the brackets at the end) and returns an object which gets assigned to myObject.</p>
<p>Because this object contains references to our private variable (privateVar is referenced inside myFunction), the JavaScript engine keeps privateVar available in memory which means myFunction can still access it using what is called a closure.  This pattern as a whole is usually called the Module Pattern.</p>
<h2>Why it's bad</h2>
<p>On the face of it, private variables sound like a good thing.  We have them in other languages after all, so why not in JavaScript too?</p>
<p>The reason that <strong>you shouldn't use the Module pattern 90% of the time you think you should</strong> is that it entirely negates the dynamic nature of the language.  If a class does 99% of what you want and you (rightly) don't want to directly modify the source code, you will be thwarted every time if the class uses this pattern.</p>
<h2>Example</h2>
<p>I'll share a recent example of this using a class in the <a href="http://extjs.com">Ext JS</a> library.  Ext is by no means the only library guilty of this, but it's the one I use on a daily basis, and this is not the only example of this problem in the library.</p>
<p>The <a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.DomQuery">Ext.DomQuery</a> object is a helper which allows us to parse XML documents locally.  Unfortunately, it suffers from a limitation which causes the text content of an XML node to be truncated if it is over a certain size limit (just 4kb in Firefox, though this differs by browser).  This isn't actually a problem of Ext's making, though it can solve it using just 1 line of code.</p>
<p>Ideally, we'd just be able to do this:</p>
<pre><code class="language-javascript">Ext.apply(Ext.DomQuery, {
  selectValue : function(path, root, defaultValue){
    path = path.replace(trimRe, "");
    if(!valueCache[path]) valueCache[path] = Ext.DomQuery.compile(path, "select");
    
    var n = valueCache[path](root), v;
    n = n[0] ? n[0] : n;
    
    //this line is the only change
    if (typeof n.normalize == 'function') n.normalize();
    
    v = (n &#x26;&#x26; n.firstChild ? n.firstChild.nodeValue : null);
    return ((v === null||v === undefined||v==='') ? defaultValue : v);
  }
});
</code></pre>
<p>All we're doing in the above is making a call to 'normalize' - a single line change which fixes the 4kb node text limitation.  Sadly though, we can't actually do this because of the use of the Module pattern.  In this example there are two private variables being accessed - 'trimRe' and 'valueCache'.</p>
<p>We can't get access to these private variables in our override, which means that our override here fails.  In fact, <strong>the Module pattern means we can't actually patch this at all</strong>.</p>
<p>The only way to do it is to modify the source code of Ext JS itself, which is a very dangerous practice as you need to remember every change you made to ext-all.js and copy them all over next time you upgrade.</p>
<p>Even if there are good reasons for enforcing the privacy of variables (in this case I don't think there are), we could get around this by providing a privileged function which returns the private variable - essentially making it read-only:</p>
<pre><code class="language-javascript">Ext.DomQuery.getValueCache = function() {
  return valueCache;
};
</code></pre>
<p>Except again this needs to be defined inside the original closure - we just can't add it later.  Again we would have to modify the original source code, with all the problems that entails.</p>
<p><a href="http://www.extjs.com/deploy/dev/docs/source/ComponentMgr.html#cls-Ext.ComponentMgr">Ext.ComponentMgr does the same trick when registering xtypes</a>.  An xtype is just a string that Ext maps to a constructor to allow for easy lazy instantiation.  The trouble is, Ext hides the xtype lookup object inside a private variable, meaning that if you have an xtype string it is <strong>impossible to get a reference to the constructor function</strong> for that xtype.  Ext provides a function to instantiate an object using that constructor, but doesn't let you get at the constructor itself.  This is totally unnecessary.</p>
<h2>Recommendations</h2>
<ul>
<li>Think very carefully before using the Module pattern at all.  Do you <strong>really</strong> need to enforce privacy of your variables? If so, why?</li>
<li>If you absolutely have to use private variables, consider providing a getter function which provides read-only access to the variables</li>
<li>Keep in mind that once defined, private variables defined this way cannot be overwritten at all.  In other languages you can often overwrite a superclass's private variables in a subclass - here you can't</li>
</ul>
<p>Either of the above would have solved both problems, but as neither was implemented we have to fall back to hackery.</p>
<p>And remember this about the Module pattern:</p>
<ul>
<li>It's <strong>overused</strong> - in the examples above (especially Ext.ComponentMgr) there is no benefit from employing the pattern</li>
<li>It's <strong>dangerous</strong> - because of its inflexibility it forces us to modify external source code directly - changes you are almost guaranteed to forget about when it comes to updating the library in the future</li>
<li>It's <strong>bloody annoying</strong> - because of both of the above.</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ExtJS modules and mixins]]></title>
            <link>https://edspencer.net//2009/10/2/extjs-modules-and-mixins</link>
            <guid>extjs-modules-and-mixins</guid>
            <pubDate>Fri, 02 Oct 2009 13:56:07 GMT</pubDate>
            <content:encoded><![CDATA[<p>A few days back <a href="http://blogs.yellowfish.biz/2009/injecting-traits-into-javascript-objects/">Praveen Ray posted about "Traits" in Ext JS</a>.  What he described is pretty much what we'd call Modules in the Ruby world, and how to mix those modules into a given class.</p>
<p>Basically, using modules we can abstract common code into reusable chunks, and then include them into one or more classes later.  This has several advantages - avoiding code repetition, decoupling code concepts and ease of unit testing among them.</p>
<p>While the idea is good, there is a better way of achieving this than Praveen suggests.  Let's say we define the following modules, which are just plain old objects:</p>
<pre><code class="language-javascript">//module providing geolocation services to a class
var GeoLocate = {
  findZipLatLng: function(zipCode) {
    //does some clever stuff to find a zip codes latitude/longitude
  },
  
  getGeoApiKey: function() {
    return this.geo_api_key || 'default key';
  }
};

//module allowing a class to act as a state machine
var StateMachine = {
  transition: function(stateName) {
    this.state = stateName;
  },
  
  inState: function(stateName) {
    return this.state == stateName;
  }
};
</code></pre>
<p>We've got a couple of fictional modules, providing geolocation and state machine functionality.  Adding these to an ExtJS class is actually pretty simple:</p>
<pre><code class="language-javascript">Ext.override(Ext.form.FormPanel, StateMachine);
Ext.override(Ext.form.FormPanel, GeoLocate);
</code></pre>
<p>All that happens above is each property of our module object is copied to Ext.form.FormPanel's prototype, making the functions available to all FormPanel instances.</p>
<p>If we just wanted to mix our modules into a specific instance of a class, we can do it like this:</p>
<pre><code class="language-javascript">var myForm = new Ext.form.FormPanel({});

Ext.apply(myForm, StateMachine);
</code></pre>
<p>This will only affect the instance we're applying to, leaving all other FormPanel instances alone.  In Praveen's example this is in fact all we need to do - there is no need to do the constructor definition and Ext.extend call, we can just use Ext.apply.</p>
<p>There's nothing in the above that's actually limited to Ext JS - all we're doing is copying properties from one object to another.  Implementing <a href="http://www.extjs.com/deploy/dev/docs/source/Ext.html#method-Ext-override">Ext.override</a> and <a href="http://www.extjs.com/deploy/dev/docs/source/Ext.html#method-Ext-apply">Ext.apply</a> are pretty simple without Ext itself, though Ext.extend is a whole other story (and a blog post in itself).</p>
<p>Finally, beware overwriting existing properties (functions or objects) on the class you are mixing into.  If your formpanel already has a 'transition' function it will be overwritten by your module, which could lead to unexpected behaviour.  At the instance level you could buy some protection against that by using Ext.applyIf instead of Ext.apply, though you might be safer writing a custom mixin function which can provide access to the original function or raise an exception when overwriting an existing property.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext.ux.layout.FillContainer]]></title>
            <link>https://edspencer.net//2009/9/28/ext-ux-layout-fillcontainer</link>
            <guid>ext-ux-layout-fillcontainer</guid>
            <pubDate>Mon, 28 Sep 2009 14:46:27 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the pages on the Ext JS app I'm currently working on has a form with a grid underneath.  The page exists as a tab inside an Ext.TabPanel, and uses the <a href="http://www.extjs.com/deploy/dev/docs/?class=Ext.layout.BorderLayout">border layout</a>, with the form as the 'north' component and the grid as 'center'.</p>
<p>The trouble with this is that the grid shrinks down to an unusable size when the browser window is too small, ending up like this:</p>
<p><img src="/images/posts/fillcontainer-1.png" alt=""></p>
<p>We could alternatively use a basic container layout, but this limits us to a fixed height for the grid, meaning we waste space at the bottom:</p>
<p><img src="/images/posts/fillcontainer-2.png" alt=""></p>
<p>Enter the imaginatively named FillContainer:</p>
<pre><code class="language-javascript">new Ext.Panel({
  autoScroll: true,
  layout: 'fillcontainer',
  items : [
    {
      html  : 'Pretend this is a form',
      height: 400
    },
    {
      html         : 'And this is the grid',
      minHeight    : 250,
      fillContainer: true
    }
  ]
});
</code></pre>
<p>If our containing panel shrinks to less than 650px in height, the grid will be automatically sized to 250px and a vertical scrollbar will appear on the panel, like this:</p>
<p><img src="/images/posts/fillcontainer-3.png" alt=""></p>
<p>If the panel's height increases to, say, 900px, the grid gets resized to 500px high.  This way we use the space when it's available, while maintaining a usable interface when height is limited:</p>
<p><img src="/images/posts/fillcontainer-4.png" alt=""></p>
<p>Here's the code that makes it work:</p>
<pre><code class="language-javascript">Ext.ns('Ext.ux.layout');

/**
 * @class Ext.ux.layout.FillContainerLayout
 * @extends Ext.layout.ContainerLayout
 * @author Ed Spencer (http://edspencer.net)
 * Extended version of container layout which expands a given child item to the 
 * full height of the container, honouring the item's minHeight property
 */
Ext.ux.layout.FillContainerLayout = Ext.extend(Ext.layout.ContainerLayout, {
  monitorResize: true,
  
  /**
   * After rendering each item, resize the one with fillContainer == true
   */
  onLayout: function(ct, target) {
    Ext.ux.layout.FillContainerLayout.superclass.onLayout.apply(this, arguments);
    
    var ctHeight    = ct.getHeight(),
        itemsHeight = 0,
        expandItem;
    
    ct.items.each(function(item) {
      if (item.fillContainer === true) {
        expandItem = item;
      } else {
        itemsHeight += item.getHeight();
      }
    });
    
    //set the expand item's height to fill the container
    if (expandItem != undefined &#x26;&#x26; ctHeight > itemsHeight) {
      var newHeight = ctHeight - itemsHeight;
      
      expandItem.setHeight(Math.max(newHeight, expandItem.minHeight));
    }
  }
});

Ext.Container.LAYOUTS['fillcontainer'] = Ext.ux.layout.FillContainerLayout;
</code></pre>
<p>As we're just extending the default container layout, your items will be rendered in the order you specify them.  The expanding item doesn't have to be the last one - we could equally have set fillContainer and minHeight on the form to expand that instead of the grid.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/fillcontainer-1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[JavaScript FizzBuzz in a tweet]]></title>
            <link>https://edspencer.net//2009/9/17/javascript-fizzbuzz-in-a-tweet</link>
            <guid>javascript-fizzbuzz-in-a-tweet</guid>
            <pubDate>Thu, 17 Sep 2009 15:15:54 GMT</pubDate>
            <content:encoded><![CDATA[<p>The <a href="http://imranontech.com/2007/01/24/using-fizzbuzz-to-find-developers-who-grok-coding/">FizzBuzz</a> challenge has been around a while but I stumbled across it again after reading <a href="http://gilesbowkett.blogspot.com/2009/09/rails-code-quality-checklist-here.html">another unique Giles Bowkett post</a>.</p>
<p>If you're not familiar with FizzBuzz, it's a little 'challenge' designed to test a candidate programmer's ability to perform a simple task.  In this case, you just have to print out the numbers from 1 to 100, unless the number is a multiple of 3, when you should instead print "Fizz", 5 in which case you print "Buzz", or both 3 and 5 in which case you print "FizzBuzz".</p>
<p>Here's a trivial JavaScript implementation:</p>
<pre><code class="language-javascript">for (var i=1; i &#x3C;= 100; i++) {
  if (i % 3 == 0) {
    if (i % 5 == 0) {
      console.log('FizzBuzz');
    } else {
     console.log('Fizz');
   }
  } else if (i % 5 == 0) {
    console.log('Buzz');
  } else {
    console.log(i);
  }
};
</code></pre>
<p>Pretty simple stuff, but a bit verbose.  I wanted something that would fit into a tweet. It turns out that's pretty simple - this is 133 characters including whitespace, 7 within tolerance for a twitter message:</p>
<pre><code class="language-javascript">for (var i = 1; i &#x3C;= 100; i++) {
  var f = i % 3 == 0, b = i % 5 == 0;
  console.log(f ? b ? "FizzBuzz" : "Fizz" : b ? "Buzz" : i);
}
</code></pre>
<p>Which of course begs the question - just how short can a JavaScript FizzBuzz implementation be? Here's my baseline, which is a tortured and contorted version of the above:</p>
<pre><code class="language-javascript">for(i=1;i&#x3C;101;i++){console.log(i%3?i%5?i:"Buzz":i%5?"Fizz":"FizzBuzz")}
</code></pre>
<p>The above is 71 characters - I expect you to do better.  The rules are that the only dependency is Firebug's console.log being available, and you can't replace 'console.log' for anything else.</p>
<p>Of course, if we did swap 'console.log' for 'alert', the whole thing would fit in a tweet twice, but that would be damn annoying.</p>
<p><strong>Hint:</strong> you can take at least three more characters off the above - can you see how?</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the ExtJS Row Editor]]></title>
            <link>https://edspencer.net//2009/9/16/using-the-extjs-row-editor</link>
            <guid>using-the-extjs-row-editor</guid>
            <pubDate>Wed, 16 Sep 2009 15:25:47 GMT</pubDate>
            <content:encoded><![CDATA[<p>The <a href="http://www.extjs.com/deploy/dev/examples/grid/row-editor.html">RowEditor plugin</a> was recently added to the <a href="http://www.extjs.com/deploy/dev/examples/">ExtJS examples page</a>.  It works a lot like a normal Grid Editor, except you can edit several fields on a given row at once before saving.</p>
<p>This neatly solves the problem of adding a new row to an editor grid, entering data into the first field and finding it save itself straight away, which is rarely desired.  In this fashion we can provide full CRUD for simple models in a single page.</p>
<h2>Installation</h2>
<p>You'll need to get a copy of the javascript, css and images from the server.  This is a bit of a pain.  If you still have the <a href="http://www.extjs.com/products/extjs/download.php">ExtJS SDK</a> around you can find these in the examples folder, if not you can get each file as follows:</p>
<p>Grab the plugin JS file below and put it where you usually put your .js files:
<a href="http://www.extjs.com/deploy/dev/examples/ux/RowEditor.js">http://www.extjs.com/deploy/dev/examples/ux/RowEditor.js</a></p>
<p>This needs to go with your other stylesheets, usually in a directory called 'css':
<a href="http://www.extjs.com/deploy/dev/examples/ux/css/RowEditor.css">http://www.extjs.com/deploy/dev/examples/ux/css/RowEditor.css</a></p>
<p>Download these two images and put them into your existing 'images' folder (the same place the other ExtJS images live):
<a href="http://www.extjs.com/deploy/dev/examples/ux/images/row-editor-bg.gif">http://www.extjs.com/deploy/dev/examples/ux/images/row-editor-bg.gif</a>
<a href="http://www.extjs.com/deploy/dev/examples/ux/images/row-editor-btns.gif">http://www.extjs.com/deploy/dev/examples/ux/images/row-editor-btns.gif</a></p>
<p>Include the .js and .css files on your page and you should be ready to go.</p>
<h2>Usage</h2>
<p>RowEditor is a normal grid plugin, so you'll need to instantiate it and add to your grid's 'plugins' property.  You also need to define what type of Editor is available (if any) on each column:</p>
<pre><code class="language-javascript">var editor = new Ext.ux.grid.RowEditor();

var grid = new Ext.grid.GridPanel({
  plugins: [editor],
  columns: [
    {
      header   : 'User Name',
      dataIndex: 'name',
      editor   : new Ext.form.TextField()
    },
    {
      header   : 'Email',
      dataIndex: 'email',
      editor   : new Ext.form.TextField()
    }
  ]
  ... the rest of your grid config here
});
</code></pre>
<p>RowEditor defines a few events, the most useful one being 'afteredit'. Its signature looks like this:</p>
<pre><code class="language-javascript">/**
 * @event afteredit
 * Fired after a row is edited and passes validation.  This event is fired
 * after the store's update event is fired with this edit.
 * @param {Ext.ux.grid.RowEditor} roweditor This object
 * @param {Object} changes Object with changes made to the record.
 * @param {Ext.data.Record} r The Record that was edited.
 * @param {Number} rowIndex The rowIndex of the row just edited
 */
'afteredit'
</code></pre>
<p>All you need to do is listen to that event on your RowEditor and save your model object appropriately.  First though, we'll define the Ext.data.Record that we're using in this grid's store:</p>
<pre><code class="language-javascript">var User = Ext.data.Record.create([
  {name: 'user_id', type: 'int'},
  {name: 'name',    type: 'string'},
  {name: 'email',   type: 'string'}
]);
</code></pre>
<p>And now the afteredit listener itself</p>
<pre><code class="language-javascript">editor.on({
  scope: this,
  afteredit: function(roweditor, changes, record, rowIndex) {
    //your save logic here - might look something like this:
    Ext.Ajax.request({
      url   : record.phantom ? '/users' : '/users/' + record.get('user_id'),
      method: record.phantom ? 'POST'   : 'PUT',
      params: changes,
      success: function() {
        //post-processing here - this might include reloading the grid if there are calculated fields
      }
    });
  }
});
</code></pre>
<p>The code above simply takes the changes object (which is just key: value object with all the changed fields) and issues a request to your server backend.  'record.phantom' returns true if this record does not yet exist on the server - we use this information above to specify whether we're POSTing to /users or PUTing to /users/123, in line with normal RESTful practices.</p>
<h2>Adding a new record</h2>
<p>The example above allows for editing an existing record, but how do we add a new one? Like this:</p>
<pre><code class="language-javascript">var grid = new Ext.grid.GridPanel({
  //... the same config from above goes here,
  tbar: [
    {
      text   : "Add User",
      handler: function() {
        //make a new empty User and stop any current editing
        var newUser = new User({});
        rowEditor.stopEditing();
        
        //add our new record as the first row, select it
        grid.store.insert(0, newUser);
        grid.getView().refresh();
        grid.getSelectionModel().selectRow(0);
        
        //start editing our new User
        rowEditor.startEditing(0);
      }
    }
  ]
});
</code></pre>
<p>Pretty simple stuff - we've just added a toolbar with a button which, when clicked, creates a new User record, inserts it at the top of the grid and focusses the RowEditor on it.</p>
<h2>Configuration Options</h2>
<p>Although not documented, the plugin has a few configuration options:</p>
<pre><code class="language-javascript">var editor = new Ext.ux.grid.RowEditor({
  saveText  : "My Save Button Text",
  cancelText: "My Cancel Button Text",
  clicksToEdit: 1, //this changes from the default double-click activation to single click activation
  errorSummary: false //disables display of validation messages if the row is invalid
});
</code></pre>
<p>If you want to customise other elements of the RowEditor you probably can, but you'll need to <a href="http://www.extjs.com/deploy/dev/examples/ux/RowEditor.js">take a look at the source</a> (it's not scary).</p>
<h2>Final Thought</h2>
<p>RowEditor is a really nice component which can provide an intuitive interface and save you writing a lot of CRUD code.  It is best employed on grids with only a few columns - for models with lots of data fields you're better off with a full FormPanel.</p>
<p>I'd be pretty happy to see this included in the default ExtJS distribution, as I find myself returning to it frequently.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Moving from Blogger to Wordpress]]></title>
            <link>https://edspencer.net//2009/9/14/moving-from-blogger-to-wordpress</link>
            <guid>moving-from-blogger-to-wordpress</guid>
            <pubDate>Mon, 14 Sep 2009 16:38:37 GMT</pubDate>
            <content:encoded><![CDATA[<p>Over the weekend I migrated from Blogger (hosted by Google) to Wordpress (hosted by me).  Overall, Wordpress feels far superior, but the migration was not without problems. Here's a short guide to what I had to do:</p>
<h2>Get Wordpress</h2>
<p><a href="http://wordpress.org/download/">First, grab the latest version of Wordpress</a>. Being a PHP application, it just drops into a directory and works :)</p>
<h2>Create a database, set config</h2>
<p>Wordpress has a setup script but being a bit of a noob I couldn't give it write permission to my filesystem.  If you are also afflicted by such inadequacies the following steps may help you.  For clarity I'll call my DB 'wordpress'.  First, set up your database:</p>
<ul>
<li>mysql -u root</li>
<li>CREATE DATABASE wordpress;</li>
<li>GRANT ALL on wordpress.* TO 'wordpress'@'localhost' identified by 'wordpress';</li>
</ul>
<p>You'll need a wp-config.php file - Wordpress comes with a default one which you can copy thusly (in the root directory of your wordpress directory):</p>
<pre><code class="language-bash">cp wp-config-sample.php wp-config.php
</code></pre>
<p>Now edit wp-config.php, and fill in the details to make it look a little like this:</p>
<pre><code class="language-bash">// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'wordpress');

/** MySQL database password */
define('DB_PASSWORD', 'wordpress');

/** MySQL hostname */
define('DB_HOST', 'localhost');
</code></pre>
<p>Hitting the site now should show a default Wordpress installation with some dummy content. Whoop.</p>
<h2>Import from Blogger</h2>
<p>Importing from Blogger is pleasingly simple - just use the Tools -> Import option on the menu.  It'll ask you to verify access and then pull down all your posts and comments.</p>
<p>It's not perfect though - for me the post Tags were imported as Categories.  To get them to be Tags again, go to the Posts -> Categories menu and use the handy Categories to Tags converter.</p>
<p>I found that the imported Post markup was pretty mangled (I think this is Blogger's fault, not Wordpress') - s everywhere and no line breaks.  To resolve this I cracked open mysql again and ran the following:</p>
<ul>
<li>use wordpress;</li>
<li>UPDATE wp_posts SET post_content = REPLACE(post_content, "", "n");
That sorted me out alright.  Next we need to set up how our permalinks work.  Set this in the Settings -> Permalinks menu, and use the following format to mimic the Blogger urls:</li>
</ul>
<p><code>/%year%/%monthnum%/%postname%.html</code></p>
<p>Wordpress will either write a .htaccess file for you at this point, or tell you it can't write to the filesystem and give you a short text config which you must manually copy into a file called .htaccess.</p>
<p>One final thing to note is that Wordpress constructs its slug urls differently to Blogger (Wordpress would use 'the-trouble-with-new' vs Blogger's 'trouble-with-new', for example).  If you're importing blog posts this means your urls won't always match, so any incoming links will be broken.  I couldn't find an easier way to correct them than just copy/paste by hand - doesn't take long though.</p>
<h3>Syntax Highlighting</h3>
<p>This whole step is entirely optional.</p>
<p>Because I'm a geek I post code fairly often.  I used the SyntaxHighlighter library back in the Blogger days and wanted to keep using it.  You can install the Wordpress plugin version from <a href="http://www.viper007bond.com/wordpress-plugins/syntaxhighlighter/">http://www.viper007bond.com/wordpress-plugins/syntaxhighlighter/</a>.  The old syntax didn't seem to work, so I needed to go back into mysql and run the following:</p>
<pre><code>
UPDATE wp_posts SET post_content = REPLACE(post_content, "&#x3C;pre name="code" class="js">", "[c0de language="js"]");
UPDATE wp_posts SET post_content = REPLACE(post_content, "&#x3C;/pre>", "[/c0de]");

</code></pre>
<p><strong>NOTE</strong>: So that Wordpress doesn't interpret those tags above I've changed the 'o' in code to a '0'.  You need to change it back :)</p>
<p>This just swaps all your old  and  tags for [c0de language="js"]and [/c0de] respectively.  Repeat the first line for any other languages you have used (for me this was xml, css, html and ruby).</p>
<h2>Fixing feeds</h2>
<p>Wordpress doesn't like the world to see your content via RSS.  Odd, isn't it?  There's an option in Settings -> Reading which claims to output the full text of each article into your feed, but it doesn't seem to work.  Instead, what you need to do is hack your theme a little.  You'll need to edit the wp-includes/feed-rss2.php file and change line 47 from <code>&#x3C;?php the_excerpt_rss() ?></code> to <code>&#x3C;?php the_content() ?></code>.</p>
<p>If you're using Feedburner or similar, don't forget to give it your new feed url too.  In this case you should also update wp-content/themes/yourTheme/header.php and swap out the  occurrences with your Feedburner url.</p>
<h2>Upload and update DNS</h2>
<p>At this point everything should be working nicely, so upload your blog folder and update your DNS settings.  I'm guessing if you're hosting Wordpress yourself you don't need help with this.  I've made my own blog into a git repository up on <a href="http://github.com/edspencer">Github</a>, allowing me to deploy any changes I make using Capistrano.  It's a nice solution - for more information see <a href="http://devblog.imedo.de/2008/6/23/wordpress-deployment-with-capistrano-2-and-git">this lovely post by the gentlemen at imedo</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The trouble with new]]></title>
            <link>https://edspencer.net//2009/9/8/trouble-with-new</link>
            <guid>trouble-with-new</guid>
            <pubDate>Tue, 08 Sep 2009 14:32:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>We have a simple JavaScript class:</p>
<pre><code class="language-javascript">
function User(firstName, lastName) {
  this.name = firstName + " " + lastName;
}
</code></pre>
<p>We create a new User:</p>
<pre><code class="language-javascript">
var ed = new User("Ed", "Spencer");
alert(ed.name); //alerts 'Ed Spencer'
alert(window.name); //undefined
</code></pre>
<p>All is well. Unless we forgot the 'new':</p>
<pre><code class="language-javascript">
var ed = User("Ed", "Spencer");
alert(ed.name); //ed is undefined
alert(window.name); //alerts 'Ed Spencer'
</code></pre>
<p>Curses! That's not what we want at all. By omitting the 'new' keyword, the JavaScript engine executes our 'User' constructor in the current scope, which in this case is the global window object.  With the scope ('this') set to window, setting 'this.name' is now the same as setting 'window.name', which is not what we're trying to do.</p>
<p>Here's the problem though, omitting the 'new' keyword is still perfectly valid syntax.  We know at design time if 'new' must be used or not, and can use a little trick to make it act as though 'new' was indeed used:</p>
<pre><code class="language-javascript">
function User(firstName, lastName) {
  if (!(this instanceof User)) {
    return new User(firstName, lastName);
  }
  
  this.name = firstName + " " + lastName;
}
</code></pre>
<p>Because the 'new' keyword sets up a new context, we can just test to see if 'this' is now an instance of our class. If it's not, it means the user has omitted the 'new' keyword, so we do it for them.  John Resig has an example of this <a href="http://ejohn.org/blog/adv-javascript-and-processingjs/">over on his blog</a>.</p>
<p>This is all very well and good, but I don't think we should use it. The reason is that we're hiding a pseudo syntax error from the developer, instead of educating them with its correct usage. If we hide this mistake in each class we write, our unknowing developer will remain unknowing, and run into a wall when they repeat their mistake on classes that don't fix it for them.</p>
<p>Instead, I suggest the following:</p>
<pre><code class="language-javascript">
function User(firstName, lastName) {
  if (!(this instanceof User)) {
    throw new Error("You must use the 'new' keyword to instantiate a new User");
  }

  this.name = firstName + " " + lastName;
}
</code></pre>
<p>The only difference of course is that we're throwing an Error instead of fixing the developer's mistake. The benefit is that their syntax won't actually work unless they write it correctly. This is good because our erstwhile developer is prompted to fix their code and understand why it was wrong. Better informed developers leads to better code.</p>
<p>Well, hopefully.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext.decorate]]></title>
            <link>https://edspencer.net//2009/8/30/extdecorate</link>
            <guid>extdecorate</guid>
            <pubDate>Sun, 30 Aug 2009 19:06:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sometimes you want to override one of the methods in ExtJS that return a configuration object - let's use Ext.direct.RemotingProvider's getCallData as an example, which looks like this:</p>
<pre><code class="language-javascript">
getCallData: function(t){
  return {
    action: t.action,
    method: t.method,
    data  : t.data,
    type  : 'rpc',
    tid   : t.tid
  };
}
</code></pre>
<p>Our aim is to add an 'authentication_token' property to the returned object. You could provide the full config object again in an override, but usually you're overriding to add, remove or change one or two properties and want to leave the rest unmolested. I used to find myself writing a lot of code with this pattern:</p>
<pre><code class="language-javascript">
//just adds an authentication token to the call data, for context see &#x3C;a href="http://www.extjs.com/forum/showthread.php?p=378912#post378912">this forum thread&#x3C;/a>
(function() {
  var originalGetCallData = Ext.direct.RemotingProvider.prototype.getCallData;
  
  Ext.override(Ext.direct.RemotingProvider, {
    getCallData: function(t) {
      var defaults = originalGetCallData.apply(this, arguments);
      
      return Ext.apply(defaults, {
        authenticity_token: '&#x3C;%= form_authenticity_token %>'
      });
    }
  })
})(); 
</code></pre>
<p>All we're really doing here is adding 1 config item - an authenticity_token, but it takes a lot of setup code to make that happen. Check out Ext.decorate:</p>
<pre><code class="language-javascript">
/**
 * @param {Function} klass The constructor function of the class to override (e.g. Ext.direct.RemotingProvider)
 * @param {String} property The name of the property the function to override is tied to on the klass' prototype
 * @param {Object} config An object that is Ext.apply'd to the usual return value of the function before returning
 */
Ext.decorate = function(klass, property, config) {
  var original = klass.prototype[property];
      override = {};
  
  override[property] = function() {
    var value = original.apply(this, arguments);
    
    return Ext.apply(value, config);
  };
  
  Ext.override(klass, override);
} 
</code></pre>
<p>This lets us write the same override like this:</p>
<pre><code class="language-javascript">
Ext.decorate(Ext.direct.RemotingProvider, 'getCallData', {
  authenticity_token: '&#x3C;%= form_authenticity_token %>'
}); 
</code></pre>
<p>Much nicer, we just tell it what we want with no need for unwieldy boilerplate code. This method doesn't actually exist in Ext (though it would be good if something similar did), but you could define it yourself as above to keep such code nice and dry.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext.ux.Printer - printing for any ExtJS Component]]></title>
            <link>https://edspencer.net//2009/7/28/extuxprinter-printing-for-any-ext</link>
            <guid>extuxprinter-printing-for-any-ext</guid>
            <pubDate>Tue, 28 Jul 2009 22:02:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>After my <a href="http://edspencer.net/2009/07/printing-grids-with-ext-js.html">recent foray</a> into printing grids with ExtJS, I realised I needed to print some trees too. Seeing as some of the work was already done for the Grid example, it made sense to create a common API for printing any Ext.Component. And thus <a href="http://github.com/edspencer/Ext.ux.Printer">Ext.ux.Printer</a> was born:</p>
<pre><code class="language-javascript">
var grid = new Ext.grid.GridPanel({ // just a normal grid });
var tree = new Ext.tree.ColumnTree({ // just a normal column tree });

Ext.ux.Printer.print(grid);
Ext.ux.Printer.print(tree);
</code></pre>
<p>Each of the above opens a new window, renders some HTML (just a big table really), prints it and closes the window - all client side with no server side code required. Although trees and grids represent data quite differently internally, we can use the same API on Ext.ux.Printer to print them both.</p>
<p>Ext.ux.Printer uses Renderer classes to cope with a specific xtype, and adding Renderers for other components is easy. At the moment Ext.grid.GridPanel and Ext.tree.ColumnTree are supported out of the box, but let's see how we'd add support for printing the contents of an Ext.Panel:</p>
<pre><code class="language-javascript">
/**
 * Prints the contents of an Ext.Panel
 */
Ext.ux.Printer.PanelRenderer = Ext.extend(Ext.ux.Printer.BaseRenderer, {

 /**
  * Generates the HTML fragment that will be rendered inside the &#x3C;html> element of the printing window
  */
 generateBody: function(panel) {
   return String.format("&#x3C;div class='x-panel-print'>{0}&#x3C;/div>", panel.body.dom.innerHTML);
 }
});

Ext.ux.Printer.registerRenderer("panel", Ext.ux.Printer.PanelRenderer);
</code></pre>
<p>This is probably the simplest print renderer of all - we're simply grabbing the HTML from inside a the panel's body and returning it inside our own div.  We subclassed Ext.ux.Printer.BaseRenderer, and in this case all we needed to do was provide an implementation for generateBody. Whatever this function returns is rendered inside the <code>&#x3C;body></code> tag of the newly-opened printing window.</p>
<p>Notice that we registered this renderer for all components with the xtype of 'panel'. Internally, Ext.ux.Printer examines the xtype chain of the component you pass it to print, and uses the first renderer that matches. As many Ext components inherit from Ext.Panel this can function as a catch-all renderer.</p>
<p>Here's how we'd use our new renderer:</p>
<pre><code class="language-javascript">
var panel = new Ext.Panel({
  html: {
    tag: 'ul',
    chidren: [
      {tag: 'li', text: 'Item 1'},
      {tag: 'li', text: 'Item 2'},
      {tag: 'li', text: 'Item 3'}
    ]
  }
});

Ext.ux.Printer.print(panel);
</code></pre>
<p>Pretty straightforward. You can now print Ext.Panels the same way you'd print a Grid or a Tree. Take a look at the <a href="http://github.com/edspencer/Ext.ux.Printer/blob/master/renderers/GridPanel.js">Grid Renderer</a> and the <a href="http://github.com/edspencer/Ext.ux.Printer/blob/master/renderers/ColumnTree.js">ColumnTree Renderer</a> for examples of rendering more advanced components.</p>
<p>As usual, <a href="http://github.com/edspencer/Ext.ux.Printer">all of the Ext.ux.Printer source</a> is available on Github, and the README file there contains instructions for installation and usage.</p>
<p>Finally, when the printing window is opened it includes a stylesheet that it expects to find at "/stylesheets/print.css". There is a default print.css stylesheet included with the extension to get you started, and you can specify where to find this stylesheet like this:</p>
<pre><code class="language-javascript">
Ext.ux.Printer.BaseRenderer.prototype.stylesheetPath = '/path/to/print/stylesheet.css';
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ExtJS grid page size - letting the user decide]]></title>
            <link>https://edspencer.net//2009/7/28/extjs-grid-page-size-letting-user</link>
            <guid>extjs-grid-page-size-letting-user</guid>
            <pubDate>Tue, 28 Jul 2009 08:28:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Sometimes you'll be using a Paging Toolbar on a grid and need to give the user the ability to change the number of records per page. One way of doing this is by adding a combobox to the toolbar:</p>
<pre><code class="language-javascript">
var combo = new Ext.form.ComboBox({
  name : 'perpage',
  width: 40,
  store: new Ext.data.ArrayStore({
    fields: ['id'],
    data  : [
      ['15'], 
      ['25'],
      ['50']
    ]
  }),
  mode : 'local',
  value: '15',

  listWidth     : 40,
  triggerAction : 'all',
  displayField  : 'id',
  valueField    : 'id',
  editable      : false,
  forceSelection: true
});
</code></pre>
<p>We've set up a simple combo box which allows the user to choose between 15, 25 and 50 records per page. Now let's set up a Paging Toolbar, and a listener to take action when the user changes the selection in the combo box:</p>
<pre><code class="language-javascript">
var bbar = new Ext.PagingToolbar({
  store:       store, //the store you use in your grid
  displayInfo: true,
  items   :    [
    '-',
    'Per Page: ',
    combo
  ]
});

combo.on('select', function(combo, record) {
  bbar.pageSize = parseInt(record.get('id'), 10);
  bbar.doLoad(bbar.cursor);
}, this);
</code></pre>
<p>Finally we'll roll it all together into a Grid:</p>
<pre><code class="language-javascript">
var grid = new Ext.grid.GridPanel({
  //your grid setup here...

  bbar: bbar
});
</code></pre>
<p>If the user needs to be able to enter her own page size, replace the ComboBox with an Ext.form.NumberField, and attach the event listener to the field's 'keypress' event.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Printing grids with Ext JS]]></title>
            <link>https://edspencer.net//2009/7/26/printing-grids-with-ext-js</link>
            <guid>printing-grids-with-ext-js</guid>
            <pubDate>Sun, 26 Jul 2009 15:43:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Grids are one of the most widely used components in Ext JS, and often represent data that the user would like to print. As the grid is usually part of a wider application, simply printing the page isn't often a good solution.</p>
<p>You could attach a stylesheet with media="print", which hides all of the other items on the page, though this is rather application-specific, and a pain to update. It would be far better to have a reusable way of printing the data from any grid.</p>
<p>The way I went about this was to open up a new window, build a table containing the grid data into the new window, then print it and close. It's actually pretty simple, and with a bit of CSS we can even get the printable view looking like it does in the grid.</p>
<p>Here's how you use it (this is a slightly modified version of the <a href="http://extjs.com/deploy/dev/examples/grid/array-grid.html">Array Grid Example</a>):</p>
<pre><code class="language-javascript">
var grid = new Ext.grid.GridPanel({
  store  : store,
  columns: [
      {header: "Company",      width: 160, dataIndex: 'company'},
      {header: "Price",        width: 75,  dataIndex: 'price', renderer: 'usMoney'},
      {header: "Change",       width: 75,  dataIndex: 'change'},
      {header: "% Change",     width: 75,  dataIndex: 'pctChange'}
      {header: "Last Updated", width: 85,  dataIndex: 'lastChange', renderer: Ext.util.Format.dateRenderer('m/d/Y')}
  ],
  title:'Array Grid',
  tbar : [
    {
      text   : 'Print',
      iconCls: 'print',
      handler: function() {
        Ext.ux.GridPrinter.print(grid);
      }
    }
  ]
});

</code></pre>
<p>So we've just set up a simple grid with a print button in the top toolbar. The button just calls Ext.ux.GridPrinter.print, which does all the rest. The full source code that this example was based upon can be found at <a href="http://extjs.com/deploy/dev/examples/grid/array-grid.js">http://extjs.com/deploy/dev/examples/grid/array-grid.js</a>.</p>
<p>The source for the extension itself is pretty simple (<a href="http://gist.github.com/raw/155789/022585f76bf6b7f3523c41395ec158f19df69f8b/Ext.ux.GridPrinter.js">download it here</a>):</p>
<p>If you look at the source above you'll see it includes a 'print.css' stylesheet, which can be used to style the printable markup. The GridPrinter expects this stylesheet to be available at /stylesheets/print.css, but this is easy to change:</p>
<pre><code class="language-javascript">
  //add this before you call Ext.ux.GridPrinter.print
  Ext.ux.GridPrinter.stylesheetPath = '/some/other/path/gridPrint.css';
</code></pre>
<p>Finally, here is some CSS I've used to achieve a grid-like display on the printable page:</p>
<pre><code class="language-css">html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}
img,body,html{border:0;}
address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}
ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}

table {
  width: 100%;
  text-align: left;
  font-size: 11px;
  font-family: arial;
  border-collapse: collapse;
}

table th {
  padding: 4px 3px 4px 5px;
  border: 1px solid #d0d0d0;
  border-left-color: #eee;
  background-color: #ededed;
}

table td {
  padding: 4px 3px 4px 5px;
  border-style: none solid solid;
  border-width: 1px;
  border-color: #ededed;
}
</code></pre>
<p>This technique could easily be adapted to print any component that uses a store - DataViews, ComboBoxes, Charts - whatever. It just requires changing the generated markup and stylesheet.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext.override - Monkey Patching Ext JS]]></title>
            <link>https://edspencer.net//2009/7/24/extoverride-monkey-patching-ext-js</link>
            <guid>extoverride-monkey-patching-ext-js</guid>
            <pubDate>Fri, 24 Jul 2009 08:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ext JS contains a function called Ext.override.  Using this function allows you to add functionality to existing classes, as well as override properties of the class. For example, let's say we want to override how Ext.Windows are hidden:</p>
<pre><code class="language-javascript">
Ext.override(Ext.Window, {
  hide: function() {
    //the contents of this function are now called instead of the default window hide function
  }
});
</code></pre>
<p>Using Ext.override changes the prototype of the class you are overriding - all instances of Ext.Window will now use the new hide function in the example above.</p>
<p>Overriding other classes can be dangerous, especially when they are classes from a library not under your control. For example, if the Ext.Window class was refactored in a later version, your overrides may no longer work. In some situations you might choose to go down the safer route of augmenting the existing functionality without overriding it. Here's one way we can achieve this using a closure:</p>
<pre><code class="language-javascript">
(function() {
  var originalHide = Ext.Window.prototype.hide;

  Ext.override(Ext.Window, {
    hide: function() {
      //perform pre-processing
      alert("The window is about to close!");

      //call the original hide function
      originalHide.apply(this, arguments);

      //perform post-processing.
      alert("The window closed!!1");
    }
  });
})();
</code></pre>
<p>In the example above we set up a closure via an anonymous function which is executed immediately. This lets us keep a reference to the original hide function on Ext.Window. Underneath we perform the override itself, in which we provide our own logic.</p>
<p>The originalHide.apply(this, arguments) line is key to maintaining Ext.Window's original functionality. By using the <a href="http://www.webreference.com/js/column26/apply.html">apply</a> keyword with the Window's usual scope ('this') and the function's <a href="http://www.devguru.com/Technologies/Ecmascript/Quickref/arguments.html">arguments</a> 'array', we can wrap our functionality before or after the original method.</p>
<p>Augmenting in this way is safer than simply overwriting the function, or copy &#x26; pasting Ext.Window's original hide function into your own, as you don't have to worry about breaking what Ext JS itself does (you're still responsible for making sure your own additions work after upgrading Ext though).</p>
<p>Be aware that this will affect <strong>all</strong> instances of Ext.Window (or whatever class you are overriding). If that isn't what you want, use <a href="http://edspencer.net/2008/08/cleaning-up-example-ext-js-form.html">Ext.extend to create your own subclasses</a> instead.</p>
<p>Finally, note that you can use Ext.override on any class, not just the built-in Ext ones - all it does internally is call Ext.apply on the constructor function's prototype.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ext JS iterator functions]]></title>
            <link>https://edspencer.net//2009/7/23/ext-js-iterator-functions</link>
            <guid>ext-js-iterator-functions</guid>
            <pubDate>Thu, 23 Jul 2009 12:46:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ext JS has a number of handy iterator functions. Some, like Ext.each, you probably already know about, but there are a few others lurking around which can be useful in saving yourself a few lines of code. First, let's recap Ext.each:</p>
<h4>Ext.each</h4>
<p>Ext.each applies a function to each member of an array. It's basically a more convenient form of a for loop:</p>
<pre><code class="language-javascript">
var people = ['Bill', 'Saul', 'Gaius'];

//using each to detect Cylons:
Ext.each(people, function(person, index) {
  var cylon = (index + 1) % 2 == 0; //every second man is a toaster
  alert(person + (cylon ? ' is ' : ' is not ') + 'a fraking cylon');
});

//is the same as
for (var i=0; i &#x3C; people.length; i++) {
  var person = people[i];
  var cylon = (i + 1) % 2 == 0; //every second man is a toaster

  alert(person + (cylon ? ' is ' : ' is not ') + 'a frakin cylon');
};
</code></pre>
<h4>Ext.iterate</h4>
<p>Ext.iterate is like Ext.each for non-array objects. Use it wherever you would normally use a for .. in loop:</p>
<pre><code class="language-javascript">
var ships  = {'Bill': 'Galactica', 'Laura': 'Colonial One'};

Ext.iterate(ships, function(key, value) {
  alert(key + "'s ship is the " + value);
});

//is the same as
for (key in ships) {
  var value = ships[key];
  alert(key + "'s ship is the " + value);
}
</code></pre>
<p>Using Ext.iterate with an array is the same as calling Ext.each. Each and Iterate both take an optional third parameter, which is the scope to run the function in. Another advantage over using the for construct is that you can easily reuse the same function:</p>
<pre><code class="language-javascript">
var myFunction = function(item, index) {
  //does some clever thing
}

Ext.each(people, myFunction);
Ext.each(['another', 'array'], myFunction);
</code></pre>
<h4>Ext.pluck</h4>
<p>Ext.pluck grabs the specified property from an array of objects:</p>
<pre><code class="language-javascript">
var animals = [
  {name: 'Ed', species: 'Unknown'},
  {name: 'Bumble', species: 'Cat'},
  {name: 'Triumph', species: 'Insult Dog'}
];

Ext.pluck(animals, 'species'); //returns ['Unknown', 'Cat', 'Insult Dog']
Ext.pluck(animals, 'name'); //returns ['Ed', 'Bumble', 'Triumph']
</code></pre>
<h4>Ext.invoke</h4>
<p>Invoke allows a function to be applied to all members of an array, and returns the results. Using our animals object from above:</p>
<pre><code class="language-javascript">
var describeAnimal = function(animal) {
  return String.format("{0} is a {1}", animal.name, animal.species);
}

var describedAnimals = Ext.invoke(animals, describeAnimal);
console.log(describedAnimals); // ['Ed is a Unknown', 'Bumble is a Cat', 'Triumph is a Insult Dog'];
</code></pre>
<p>Ext.invoke performs a similar job to Ruby's collect method in making it easy to transform arrays. Any additional arguments passed to the Ext.invoke call will be passed as arguments to your function, in this case the describeAnimal function. Obviously your functions will be much more grammatically accurate than mine.</p>
<h4>Ext.partition</h4>
<p>Ext.Partition splits an array into two sets based on a function you provide:</p>
<pre><code class="language-javascript">
var trees = [
  {name: 'Oak',    height: 20},
  {name: 'Willow', height: 10},
  {name: 'Cactus', height: 5}
];

var isTall = function(tree) {return tree.height > 15};

Ext.partition(trees, isTall);

//returns:
[
  [{name: 'Oak', height: 20}], 
  [{name: 'Willow', height: 10}, {name: 'Cactus', height: 5}]
]
</code></pre>
<p>The partition call above returns a 2-dimensional array with the first element containing all of the items for which the function returned true (tall trees in this case), and the second containing items for which the function return false.</p>
<h4>Math functions</h4>
<p>Finally, we have some simple math-related functions:</p>
<pre><code class="language-javascript">
var numbers = [1, 2, 3, 4, 5];
Ext.min(numbers); //1
Ext.max(numbers); //5
Ext.sum(numbers); //15
Ext.mean(numbers); //3
</code></pre>
<p>While the built in functions don't cater for all situations, they're useful to have and to know about, and usually offer a more elegant approach than using the 'for' keyword.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Read my BDD article in this month's JS Magazine]]></title>
            <link>https://edspencer.net//2009/6/10/read-my-bdd-article-in-this-months-js</link>
            <guid>read-my-bdd-article-in-this-months-js</guid>
            <pubDate>Wed, 10 Jun 2009 15:44:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I have an article on Behaviour Driven Development for JavaScript in June's edition of <a href="http://www.jsmag.com/">the excellent JavaScript Magazine</a>.</p>
<p>If you haven't seen or read the magazine before (it's quite new), it's well worth the few dollars charged.  The magazine format allows for in-depth articles that require more space, time and effort to write than a typical blog post, and which therefore often go unwritten.</p>
<p>The thrust of my article is that too much of our JavaScript goes untested, but that nowadays it's easy to fix that. I go through an example of a client side shopping cart, using the <a href="http://jspec.info">JSpec BDD</a> library. Even if you don't buy/read the magazine, I highly recommend <a href="http://jspec.info">checking out JSpec</a> and <a href="http://github.com/nathansobo/screw-unit/tree/master">other libraries</a> like it. As JavaScript powered applications become the norm, BDD will only become more important in ensuring our applications work properly, so now is a good time to start.</p>
<p>Also in this month's issue is a guide to using the Canvas tag, tips on how to use build scripts to optimise your JavaScript for each environment, AJAX security pointers and a roundup of community news.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Darwin, Humanism and Science]]></title>
            <link>https://edspencer.net//2009/6/7/darwin-humanism-and-science</link>
            <guid>darwin-humanism-and-science</guid>
            <pubDate>Sun, 07 Jun 2009 16:17:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>On Saturday I had the good fortune to be able to attend a conference entitled "<a href="http://www.humanism.org.uk/meet-up/events/view/20">Darwin, Humanism and Science</a>", held at London's Conway Hall.  For those not able to attend here is a short roundup of what happened:</p>
<h2>Richard Dawkins starts us off</h2>
<p>The conference kicked off with a quick introduction from <a href="http://www.humanism.org.uk/">BHA</a> President Polly Toynbee, after which Professor Dawkins took to the stage.  His lecture revolved around the concluding paragraph of Darwin's On the Origin of Species, which can be read online for free <a href="http://darwin-online.org.uk/content/frameset?viewtype=text&#x26;itemID=F373&#x26;pageseq=508">here</a> (the relevant passage starts "Thus, from the war of nature ..."). Dawkins analysed each segment of the text in turn, giving us his insights into its meaning and slipping in some fascinating information about our modern-day understanding of evolution, such as how we know that all species in the world today must be descended from a single progenitor.</p>
<p>The professor left some time for questions at the end of his lecture. He had commented on the lamentable state of the public's understanding of science, proffering the alarming statistic that some 18% of the British population believes that the Earth orbits the Sun once a month (presumably we go around faster in February), which lead me to ask him what we can do to combat this.  His answer was to "get out more".</p>
<p>Referring to the role scientists have to play in the public's awareness and understanding of science, rather than (I hope) to my social life, he made the point that scientists and educators must make greater efforts to reach out to the public and disseminate not only the knowledge that modern science has obtained, but the joy that this knowledge can bring. It was a point that was returned to time and again throughout the day.</p>
<h2>Insidious Creationism in Education</h2>
<p>Following Professor Dawkins were two quick talks about the teaching of evolution in schools - first from a European perspective from Professor Charles Susanne, and then from a British one from James Williams.  Though both highlighted the growing influence Creationist organisations are having on educational materials, Mr Williams' speech was for me the more alarming:</p>
<p>Quickly firing through a series of ridiculous materials showing how children and dinosaurs once lived and played together, and even an endearing image of Jesus cuddling a small Velociraptor, Williams showed how creationist books, comics and literature represent an "intellectual abuse of children".  Entitled "Insidious Creationism", his talk opened our eyes to the battle being waged over children's education, in this country and around the world. I for one am very pleased that we have people like Williams fighting in our corner, and for his troubles he was presented with the rather dubious prize of an <a href="http://richarddawkins.net/article,3239,UPDATED-Richard-Dawkins-on-Harun-Yahyas-Atlas-of-Creation,Richard-Dawkins-Council-of-Ex-Muslims-of-Britain">Atlas of Creation</a>.</p>
<h2>Human understanding of Evolution</h2>
<p>After lunch we were treated to talks from Johan De Smedt and Dr Michael Schmidt-Salomon. Johan's talk revolved around the three themes of essentialism, teleology and the design stance. Tackling these in turn he described the biases inherent within us that give these ideas more prominence in our mental model of the world than they deserve, especially when we are young.</p>
<p>Dr Schmidt-Salomon rebutted the idea that evolution can be objected to on moral grounds. Though it may seem obvious that a moral objection to a natural phenomenon does not make it any less real, he reminds us that there are those who disagree. His most impressive moment though was in revealing his efforts to turn Ascension Day into Evolution Day, aided by a spectacularly bizarre music video featuring Charles Darwin as an unlikely rock star:</p>
<p>[youtube http://www.youtube.com/watch?v=wbIa9fZuTFA&#x26;hl=en&#x26;fs=1&#x26;]</p>
<p>Brilliant.</p>
<h2>Hinduism and The Two Cultures</h2>
<p>For me an unexpected highlight of the day came in the form of Babu Gogineni's description of the devastating effect that some interpretations of Hinduism are having on science in India.  He described how many Hindus believe that modern science backs up Hinduism's central tenets, and can therefore turn their backs on further progress made by the scientific community.  It was a startling and eye-opening description, and one could feel his frustration at how quackery and superstition are considered more important (or at least more profitable) than science and understanding in India today.</p>
<p>The conference was rounded off by what seemed a short talk from Professor A C Grayling. This was the first time I had heard Grayling speak, and the calm lucidity with which he spoke made his 45 minutes seem more like 5.  Speaking on the 'two cultures' - that of science and that of the humanities - he referred to a <a href="http://en.wikipedia.org/wiki/The_Two_Cultures">lecture given by C P Snow</a> some 50 years ago decrying the divergence of these two cultures, and the widening communication gap between them.  Snow's original point was that this divergence was getting in the way of solving the world's problems - 50 years later Grayling points out that we still have some way to go in closing that gap.</p>
<h2>The 'Special' Dinner</h2>
<p>In the evening a special dinner was laid on for the speakers and delegates attending the conference (today was just one day in a week of Humanism conferences). For some reason they let some of the unwashed masses in too, and so it was that I sat down with a delightful group of fellow conference-goers to enjoy a good meal punctuated by good conversation.</p>
<p>After we had eaten Professor Grayling presented Professor Dawkins with an award in recognition of his efforts in spreading rationality and clear thinking around the world, and in return Dawkins read out a modern day episode of <a href="http://en.wikipedia.org/wiki/Jeeves_and_Wooster">Jeeves and Wooster</a>, albeit with a rather Atheistic stance.  I'm not sure whether or not he penned the parable himself but it was extremely well written and its British humour well received.</p>
<p>All too soon the coffee came round and it was time to leave. Overall the day was very well run and extremely enjoyable. A wise gentleman on my table was moved to remark that "it was the best 8 quid I've ever spent". Amen.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA['function' in JavaScript - operator vs statement]]></title>
            <link>https://edspencer.net//2009/4/29/function-in-javascript-operator-vs</link>
            <guid>function-in-javascript-operator-vs</guid>
            <pubDate>Wed, 29 Apr 2009 13:54:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In JavaScript we have at least 4 ways of defining functions:</p>
<pre><code class="language-javascript">function myFunction() { alert('hai!'); }
var myFunction = function() { alert('hai!'); }
var myFunction = function myFunctionName() { alert('hai!'); }
var myFunction = new Function("alert('hai!');")
</code></pre>
<p>These are not all the same, and the crucial thing here is the word 'function' as used in each case.  In the first example we're using the function <strong>statement</strong>, and in the second and third examples we're using the function <strong>operator</strong>.  We'll come back to the fourth example later.</p>
<p>So what's the difference between the function statement and the function operator?  Well first we need to understand a bit about anonymous functions.  Most of us are familiar with using anonymous functions as event listeners - something like this:</p>
<pre><code class="language-javascript">this.on('render', function() {... do some stuff when 'this' has rendered ...});
</code></pre>
<p>In this example we've passed in a function without a <strong>name</strong> as a listener callback.  But what do we mean when we say a function does have a name?  Do we mean this:</p>
<pre><code class="language-javascript">var myFunction = function() {... do some stuff ...};
</code></pre>
<p>No we don't.  Assigning a function to a variable does not give it a name.  The function assigned to our variable above is still an anonymous function.  To give a function a name we need to do something like this:</p>
<pre><code class="language-javascript">var myFunction = function myFunctionName() {... do some stuff ...};
</code></pre>
<p>Now we have declared a function with the <strong>name</strong> myFunctionName and assigned it to the <strong>variable</strong> myFunction.  Giving a function a name in this way adds a read-only name property to it:</p>
<pre><code class="language-javascript">var myVar = function captainKirk() {... do some stuff ...};

alert(myVar.name); //alerts 'captainKirk'

//we can't update it though
myVar.name = 'williamShatner';
alert(myVar.name); //still 'captainKirk'
</code></pre>
<p>Coming back to our very first example, we can see that we're using a different form here - the function <strong>statement</strong>:</p>
<pre><code class="language-javascript">function myFunction() { alert('hai!'); }
</code></pre>
<p>Under the hood, what this is actually doing is something like this:</p>
<pre><code class="language-javascript">
myFunction = function myFunction() { alert('hai!'); }
</code></pre>
<p>The function <strong>statement</strong> created a named function and assigns it to a variable of the same name.  Note that in this case although the function name and the variable name are the same, they don't have to be:</p>
<pre><code class="language-javascript">function myFunction() { alert('hai!'); }
alert(myFunction.name); //alerts 'myFunction'

//assigning this function to another variable preserves the function name
var myVar = myFunction;
alert(myVar.name); //alerts 'myFunction'
</code></pre>
<p>Let's take a look at the last of our four initial examples:</p>
<pre><code class="language-javascript">var myFunction = new Function("alert('hai!');")
</code></pre>
<p>Functions defined this way are always anonymous, and cannot be given a name.  In general you shouldn't define functions this way, for several reasons:</p>
<ul>
<li>The function body has to be parsed by the JS engine every time it is run, compared to just once for a normal function definition. This is slow</li>
<li>Functions defined this way do not inherit the current scope.  If you define a function this way the only scope it inherits is the global scope, which means it does not have access to any variables or functions in your current scope chain</li>
<li>Defining functions this way requires the body to be entered as a string, which should sicken you enough not to use it.</li>
</ul>
<p>One last thing to note is that if you use the function operator, it has to be within the context of an expression.  For example you can't do this:</p>
<pre><code class="language-javascript">function() {alert('hai!');}
</code></pre>
<p>That doesn't work because it's not part of an expression - the function isn't being assigned to anything and you get a syntax error.  If you want to run an anonymous function and not assign it to a variable, it can be done like this, which runs the function straight away:</p>
<pre><code class="language-javascript">(function() {alert('hai!');})();
</code></pre>
<p>For further reading on this check out <a href="https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Functions">the Mozilla function reference docs</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The case for Ext.applyOnly]]></title>
            <link>https://edspencer.net//2009/4/23/case-for-extapplyonly</link>
            <guid>case-for-extapplyonly</guid>
            <pubDate>Thu, 23 Apr 2009 09:49:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Update:</strong> Ext 3.0RC1 has included something like this, but called Ext.copyTo.  Obviously my name is better though.</p>
<p>We should have something like this:</p>
<pre><code class="language-javascript">
Ext.applyOnly(this, config, ['width', 'height']);
</code></pre>
<p>You could use this every time you write a method or class that requires a config Object as one of it's parameters.  These methods ought to only apply those properties of the config object they actually need, but usually this will just be done with an Ext.apply(this, config).  This means anything in your object could be overwritten by this config object. Sometimes that's a good thing, but sometimes it's definitely not.</p>
<p>Ext.applyOnly() applies only a whitelist of the properties in the config object.  These are specified by an optional third argument, which is an array of property names. Here's how you might write applyOnly:</p>
<pre><code class="language-javascript">
/**
 * Applies only a pre-specified set of properties from one object to another
 * @param {Object} receiver The object to copy the properties to
 * @param {Object} sender The object to copy the properties from
 * @param {Array} whitelist The whitelist of properties to copy (e.g. ['width', 'height'])
 * @return {Object} The receiver object, with any of the whitelisted properties overwritten if they exist in sender
 */
Ext.applyOnly = function(receiver, sender, whitelist) {
  if (receiver &#x26;&#x26; sender) {
    Ext.each(whitelist || [], function(item) {
      if (typeof sender[item] != 'undefined') receiver[item] = sender[item];
    }, this);
  };
  
  return receiver;
};
</code></pre>
<p>While you can't stop code maliciously overwriting properties this way, it would stop people from unknowingly overwriting your object's properties.  They could overwrite them manually, but they'll do this knowing that this wasn't an intended use for the class. Let's have a look at an extension that would do a great job opening popups telling people they've won lots of money:</p>
<pre><code class="language-javascript">
/**
 * Pops up windows telling lucky visitor she's won big money!!!!
 */
Ext.ux.WinnerEarnings = Ext.extend(Ext.util.Observable, {
  /**
   * @property accessibleProperties
   * @type Array
   * All properties intended to be mass-updatable
   */
  accessibleProperties: ['height', 'width'],
  
  /**
   * Message to show lucky winners!! You can't change this!!!!!
   */
  message: "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!",
  
  constructor: function(config) {
    //apply only the fields that are deemed writable
    Ext.applyOnly(this, config, this.accessibleProperties);
    
    Ext.ux.WinnerEarnings.superclass.constructor.apply(this, arguments);
    
    Ext.applyIf(this, {
      version: 2.9,
      coolFeature: Ext.util.TaskRunner({
        interval: 1000,
        scope:    this,
        run: function() {
          //version, coolFeature, updateDetails, closable and close won't be sent to Ext.Window
          new Ext.Window(Ext.applyOnly({}, this, ['height', 'weight', 'message'])).show();
        }
      }).start()
    }
  },
  
  /**
   * Updates this WinnerEarnings opportunity with options from the supplied object
   * @param {Object} updates An object containing updates to make to this precious opportunity
   * @return {Ext.ux.WinnerEarnings} The WinnerEarnings object
   */
  updateDetails: function(updates) {
    return Ext.applyOnly(this, updates, this.accessibleProperties)
  },
  
  //secret tricks to let the user stop the popups
  closable: false,
  close: function() {
    this.coolFeature.stop();
  }
})
</code></pre>
<p>How it works:</p>
<pre><code class="language-javascript">
var myObj = new Ext.ux.WinnerEarnings({height: 200, width: 150});

myObj.updateDetails({width: 300, message: "My Message"})
myObj.width:   // => 300
myObj.message; // => "You're the 1000000000000th visitor!!!!!!!1 Click here to claim money. Now!!!"

//updating message didn't work, but we can still do it manually
myObj.message = "My message";
myObj.message; // => "My message"
</code></pre>
<p>In my example class I've added the whitelist as an accessibleProperties property on the class, which makes it easy for others to see what they should and should not be updating.</p>
<p>In this example we're also sanitizing output with applyOnly.  WinnerEarnings lightly wraps around a series of Ext.Windows and we'd like to be able to pass our WinnerEarnings object as config. We want to make sure we're not passing our 'closable' property, 'close()' function and others to the Ext.Window constructor, so we pass that in via a whitelist too, inside the run() function in our constructor.</p>
<p><a href="http://github.com/edspencer/ext.applyonly/blob/98bf85dc08b71d774a4b0d5d14b5153ad5250970/applyOnly.spec.js">Check out the unit tests</a> for the function to see a couple more use cases. Here's one final example - sanitizing output from a function:</p>
<pre><code class="language-javascript">
myFunction = function(input) {
  //do some stuff to make input useful
  
  //guarantee our returned object only has relevant properties
  return Ext.applyOnly({}, input, ['important-thing-1', 'important-thing-2']);
}
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Force Ext.data.Store to use GET]]></title>
            <link>https://edspencer.net//2009/2/11/force-extdatastore-to-use-get</link>
            <guid>force-extdatastore-to-use-get</guid>
            <pubDate>Wed, 11 Feb 2009 16:23:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Say you have a simple Ext store:</p>
<pre><code class="language-javascript">
var myStore = new Ext.data.Store({
  url:    '/widgets.json',
  reader: someReader
});
</code></pre>
<p>Which you put in a grid, along with a paging toolbar:</p>
<pre><code class="language-javascript">
var myGrid = new Ext.grid.GridPanel({
  store:   myStore,
  columns: [.....],
  bbar:    new Ext.PagingToolbar({
    store: myStore
  })
  ... etc ...
});
</code></pre>
<p>Your grid loads up and the store performs a GET request to /widgets.json, which returns your widgets along with a total (<a href="http://extjs.com/deploy/dev/examples/grid/paging.html">see an example</a>).</p>
<p>Awesome, but now we click one of the paging buttons on the PagingToolbar and we have a problem - our request has turned into POST /widgets.json, with "start=20" and "limit=20" as POST params.</p>
<p>Now we don't really want that - we're not POSTing any data to the server after all, we're just trying to GET some.  If you're using a nice RESTful API on your server side this may cause you a real problem, as POST /widgets will likely be taken as an attempt to create a new Widget.</p>
<p>Luckily, as with most things the solution is simple if you know how.  An <a href="http://extjs.com/deploy/dev/docs/?class=Ext.data.Store">Ext.data.Store</a> delegates loading its data off to an <a href="http://extjs.com/deploy/dev/docs/?class=Ext.data.DataProxy">Ext.data.DataProxy</a> subclass.  By default your store will create an <a href="http://extjs.com/deploy/dev/docs/?class=Ext.data.HttpProxy">Ext.data.HttpProxy</a> using the url: '/widgets.json' you passed in your store config.  To make sure your stores are always requesting data using GET, just provide a proxy like this:</p>
<pre><code class="language-javascript">
var myStore = new Ext.data.Store({
  proxy: new Ext.data.HttpProxy({
    url:    '/widgets.json',
    method: 'GET'
  }),
  reader: someReader
});
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding a loading mask to your ExtJS application]]></title>
            <link>https://edspencer.net//2009/2/1/adding-loading-mask-to-your-extjs</link>
            <guid>adding-loading-mask-to-your-extjs</guid>
            <pubDate>Sun, 01 Feb 2009 16:55:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Adding a loading mask like the one on the <a href="http://extjs.com/deploy/dev/docs/">ExtJS API application</a> is a nice way of showing the user that something is happening while their browser downloads the source code.  It's also extremely easy to do.</p>
<p>First, place the following HTML above all of your javascript include tags, ideally just after the <code>&#x3C;body></code> tag:</p>
<pre><code class="language-html">&#x3C;div id="loading-mask">&#x3C;/div>
&#x3C;div id="loading">
  &#x3C;div class="loading-indicator">
    Loading...
  &#x3C;/div>
&#x3C;/div>
</code></pre>
<p>If you are currently including javascript files inside the <code>&#x3C;head></code>, don't - <a href="http://developer.yahoo.com/performance/rules.html#js_bottom">put them at the bottom</a>.</p>
<p>With a bit of CSS (see below), this provides a white mask over all underlying content, and a loading message.  When everything has loaded, remove the mask like this:</p>
<pre><code class="language-javascript">
Ext.onReady(function() {
  setTimeout(function(){
    Ext.get('loading').remove();
    Ext.get('loading-mask').fadeOut({remove:true});
  }, 250);
});
</code></pre>
<p>The above simply fades out the HTML elements to reveal the now ready page.  The setTimeout call gives your app a little time to render, which is useful if you're doing something like pulling external content down from the server.</p>
<p>Finally, here's the CSS I use to style up the loading mask.  You'll need to <a href="http://extjs.com/deploy/dev/docs/resources/extanim32.gif">download a loading image</a> and stick it in the appropriate directory.</p>
<pre><code class="language-css">#loading-mask {
  position: absolute;
  left:     0;
  top:      0;
  width:    100%;
  height:   100%;
  z-index:  20000;
  background-color: white;
}

#loading {
  position: absolute;
  left:     50%;
  top:      50%;
  padding:  2px;
  z-index:  20001;
  height:   auto;
  margin:   -35px 0 0 -30px;
}

#loading .loading-indicator {
  background: url(../images/loading.gif) no-repeat;
  color:      #555;
  font:       bold 13px tahoma,arial,helvetica;
  padding:    8px 42px;
  margin:     0;
  text-align: center;
  height:     auto;
}
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why you should be using History in your ExtJS applications]]></title>
            <link>https://edspencer.net//2009/1/23/why-you-should-be-using-history-in-your</link>
            <guid>why-you-should-be-using-history-in-your</guid>
            <pubDate>Fri, 23 Jan 2009 00:59:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've been making a few updates to the ExtJS API documents application recently.  The actual updates include remembering which tabs you have open and using Ext.History to go between tabs (you can follow the <a href="http://extjs.com/forum/showthread.php?t=57616">forum post</a> or <a href="http://extjs.edspencer.net/extjs/docs/">see a beta version</a>).</p>
<p>That's not quite ready yet, but what has been made very clear to me is that any ExtJS application with more than one view should be using Ext.History.  With History we get urls inside the application itself, we can parse them and dispatch accordingly.  For example, I'm using a Rails-like Router, which lets you define an internal url map like this:</p>
<pre><code class="language-javascript">
map.connect(":controllers/:action/:id");
map.connect(":controllers/:action");
</code></pre>
<p>The router knows how to decode urls based on the regular expression-like syntax above, and parse the matches into an object - for example:</p>
<pre><code class="language-javascript">
#users/new    &#x3C;= becomes {controller: 'users', action: 'new'}
#users/edit/2 &#x3C;= becomes {controller: 'users', action: 'edit', id: 2}
#colours      &#x3C;= becomes {controller: 'colours'}
</code></pre>
<p>You can of course define any url matching scheme using the connect() function.  I then use a simple Dispatcher, which looks at the decoded parameters.  It finds the appropriate controller and calls that action on the controller, passing any other parameters as arguments. For example:</p>
<pre><code class="language-javascript">
#users/new      &#x3C;= calls UsersController's "new" action
#colours/edit/2 &#x3C;= calls ColoursController's "edit" action, with {id: 2} as the argument
</code></pre>
<p>And so on.  Each controller knows what to do for that action.  It's easy then to say to someone "go to http://myapp.com/admin#users/152/comments" - which will take them straight to the comments that user 152 has written.  Compare that with saying: "go to http://myapp.com/admin, then click the List Users tab, then find the user called Joe Bloggs, then double click the bubble icon next to his name".  It's obvious which approach is better.</p>
<p>You don't even need to use something as elaborate as a router, just a simple switch statement or some regular expressions would be enough for many applications.  Once you've got <a href="http://edspencer.net/2009/01/using-exthistory.html">Ext.History setup</a>, you could do something as simple as:</p>
<pre><code class="language-javascript">
//decodes a url and decides how to dispatch it
dispatch = function(token) {
  switch (token) {
    case "users"    :   displayUsers();   break;
    case "users/new":   displayNewUser(); break;
    case "users/2/edit: editUser(2);      break;
    default:            displayDefault(); break;
  };
};
Ext.History.on('change', dispatch);

//Call dispatch on initial page load as Ext.History's change event is not fired here
Ext.History.init(function() {
  var token = document.location.hash.replace("#", "");
  dispatch(token);
});
</code></pre>
<p>Obviously you don't hard code user IDs like that but it's easy to see how to roll your own.  With just a few lines of code, you've decoded a url into a function to call, which can do anything you need it to.  All your internal navigation needs to do is call Ext.History.add("some/new/url"), which will now be picked up by your dispatch code.</p>
<p>It's important to only route like this for <a href="http://en.wikipedia.org/wiki/Idempotent">idempotent</a> actions (i.e. actions which display data rather than change it), so that data changing actions are not repeated.  This is equivalent to using GET and POST correctly in normal web applications.</p>
<p>When the simplest implementation takes just a few lines of code, what reason could there be not to be using it?</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ExtJS Solitaire]]></title>
            <link>https://edspencer.net//2009/1/13/extjs-solitaire</link>
            <guid>extjs-solitaire</guid>
            <pubDate>Tue, 13 Jan 2009 20:22:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><strong>Update</strong>: We recently released the <a href="http://edspencer.net/2010/06/offline-apps-with-html5-a-case-study-in-solitaire.html">updated Touch Solitaire</a> for <a href="http://www.sencha.com/products/touch/">Sencha Touch</a>.</p>
<p><a href="http://solitaire.edspencer.net"><img src="/images/posts/solitaire.png" alt=""></a></p>
<p>For a bit of fun over Christmas I thought I'd try my hand at writing Solitaire using the ExtJS library.  The results of my efforts can be seen over at <a href="http://solitaire.edspencer.net">http://solitaire.edspencer.net</a>.</p>
<p>It's reasonably complete, with the familiar drag and drop moving of cards (and stacks of cards).  Most of the interface is custom built, with classes representing Cards, Stacks, the Pack, etc.  The main motivation for creating this is to give a real-world example of using Drag and Drop with Ext JS, as documentation for it can be hard to come by.  The full source of the game can be found <a href="http://github.com/edspencer/extjs-solitaire">on github</a>, and I encourage people to take a look at and/or improve the code if they wish.</p>
<p>A few stats: the game comes to 1300 lines of code, including generous comments and whitespace.  It's 15k minified, and uses a custom Ext build.  It took roughly 25 hours to put together, which was mostly spent researching how to use Ext's many D&#x26;D classes.</p>
<p>The reason I'm releasing it now is that I'm currently working on a much larger, more exciting open source ExtJS project which I want to concentrate on before releasing.  If anyone wants to pick this up feel free to fork the code on Github or get in touch in the comments or in #extjs on IRC.</p>]]></content:encoded>
            <enclosure url="https://edspencer.net/images/posts/solitaire.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ExtJS Textmate bundle]]></title>
            <link>https://edspencer.net//2009/1/10/extjs-textmate-bundle</link>
            <guid>extjs-textmate-bundle</guid>
            <pubDate>Sat, 10 Jan 2009 15:58:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>** Update 2:** I've recently cleaned up the bundle, removing stale snippets. It's now located at https://github.com/edspencer/Sencha.tmbundle</p>
<p>** Update:** Added extra instructions when downloading the bundle instead of git cloning it.  Thanks to <a href="http://www.linkedin.com/in/topkatz">TopKatz</a> for his help</p>
<p>I develop on both OSX and Windows machines, and my editors of choice are <a href="http://macromates.com/">Textmate</a> and the excellent Windows clone <a href="http://e-texteditor.com">E</a>.  One of the great things about Textmate is its bundle support, which allows you to create reusable code snippets (among other things).</p>
<p>I've got a good collection of these built up so thought I'd make them available on <a href="http://github.com/edspencer/Sencha.tmbundle/tree/master">Github</a>.  You can install it like this:</p>
<pre><code>Mac OSX:

cd ~/Library/Application Support/TextMate/Bundles
git clone git://github.com/edspencer/Sencha.tmbundle.git

Windows:

cd C:Documents and Settings{YOUR USERNAME}Application DataeBundles
git clone git://github.com/edspencer/Sencha.tmbundle.git
</code></pre>
<p>If you don't have git installed you can simply <a href="http://github.com/edspencer/Sencha.tmbundle/zipball/master">download the bundle as a zip file</a>, and extract it into the directory as above.  You need to rename the extracted directory to something like extjs.tmbundle or it won't show up.  If you do go the git route you can of course cd into that git directory at any point and use git pull to update to the latest bundle version.</p>
<p>I'll give one example of the usefulness of snippets like these; here's the Ext.extend snippet from the bundle:</p>
<pre><code class="language-javascript">/**
 * @class ${1:ClassName}
 * @extends ${2:extendsClass}
 * ${5:Description}
 */
${1:ClassName} = function(config) {
  var config = config || {};
 
  Ext.applyIf(config, {
    $0
  });
 
  ${1:ClassName}.superclass.constructor.call(this, config);
};
Ext.extend(${1:ClassName}, ${2:extendsClass});

${3:Ext.reg('${4:xtype}', ${1:ClassName});}
</code></pre>
<p>To use this you can just type 'extend' into a JS file in TextMate/E and press tab.  The snippet takes you through a few editable areas such as the name of your new class, the name of the class you're extending, xtype definition and description, then dumps the cursor inside the Ext.applyIf block.  The actual characters typed are these:
<code>extend [tab] MyWindow [tab] Ext.Window [tab] [tab] mywindow [tab] Special window class [tab]</code></p>
<p>Which produces this:</p>
<pre><code class="language-javascript">
/**
 * @class MyWindow
 * @extends Ext.Window
 * Special window class
 */
MyWindow = function(config) {
  var config = config || {};
 
  Ext.applyIf(config, {
    
  });
 
  MyWindow.superclass.constructor.call(this, config);
};
Ext.extend(MyWindow, Ext.Window);

Ext.reg('mywindow', MyWindow);
</code></pre>
<p>Hopefully it's obvious how much time things like this can save when generating repetitive, boilerplate code.  The extend snippet is one of the larger ones but even the small ones are very useful (pressing c then tab is much nicer than typing console.log(''); each time).</p>
<p>Any suggestions/contributions are welcome.  Thanks go to <a href="http://github.com/rdougan">rdougan</a> for his contributions and organisation also.</p>
<p>There is also another ExtJS textmate bundle available at <a href="http://hakore.com/extjs.tmbundle/">http://hakore.com/extjs.tmbundle/</a>, written by <a href="http://extjs.com/forum/member.php?u=36775">krzak</a> from the Ext forums.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Ext.History]]></title>
            <link>https://edspencer.net//2009/1/9/using-exthistory</link>
            <guid>using-exthistory</guid>
            <pubDate>Fri, 09 Jan 2009 12:17:00 GMT</pubDate>
            <content:encoded><![CDATA[<p><a href="http://extjs.com/deploy/dev/docs/?class=Ext.History">Ext.History</a> is a small class that was released with ExtJS 2.2, making it easy to use the browser's back and forward buttons without breaking your AJAX-only pages.</p>
<p>This can be really useful for any ExtJS application with more than one view, for example a simple app with a grid of Products, which can be double-clicked to reveal an edit form.  Ext.History allows the user to click the back button to go back to the grid if they're on the form, and even forward again from the grid.  It does this by appending a token to the end of the url:</p>
<pre><code class="language-xml">http://myurl.com/ (default url for the app)
http://myurl.com/#products (shows the products grid)
http://myurl.com/#products/edit/1 (shows the edit form for product 1)
</code></pre>
<p>This is useful, so let's look at how to set it up.  Ext.History requires that a form field and an iframe are present in the document, such as this:</p>
<pre><code class="language-xml">&#x3C;form id="history-form" class="x-hidden" action="#">
  &#x3C;div>
    &#x3C;input id="x-history-field" type="hidden" />
    
  &#x3C;/div>
&#x3C;/form>
</code></pre>
<p>The div is just there to make the markup valid.  Ext.History uses the iframe to make IE play nice.  Generally I don't like to make any assumptions about what is in the DOM structure so I use Ext to generate these elements:</p>
<pre><code class="language-javascript">
/**
* Creates the necessary DOM elements required for Ext.History to manage state
* Sets up a listener on Ext.History's change event to fire this.handleHistoryChange
*/
initialiseHistory: function() {
  this.historyForm = Ext.getBody().createChild({
    tag:    'form',
    action: '#',
    cls:    'x-hidden',
    id:     'history-form',
    children: [
      {
        tag: 'div',
        children: [
          {
            tag:  'input',
            id:   Ext.History.fieldId,
            type: 'hidden'
          },
          {
            tag:  'iframe',
            id:   Ext.History.iframeId
          }
        ]
      }
    ]
  });

  //initialize History management
  Ext.History.init();
  Ext.History.on('change', this.handleHistoryChange, this);
}
</code></pre>
<p>Ext.History.fieldId and Ext.History.iframeId default to 'x-history-field' and 'x-history-frame' respectively.  Change them before running initialiseHistory if you need to customise them (Ext.History is just a singleton object so you can call Ext.History.fieldId = 'something-else').</p>
<p>The main method you'll be using is Ext.History.add('someurl').  This adds a token to the history stack and effectively redirects the browser to http://myurl.com/#someurl.  To create something like the grid/form example above, you could write something like this:</p>
<pre><code class="language-javascript">
Ext.ns('MyApp');

MyApp.Application = function() {
  this.initialiseHistory();

  this.grid = new Ext.grid.GridPanel({
    //set up the grid...
    store: someProductsStore,
    columns: ['some', 'column', 'headers'],

    //this is the important bit - redirects when you double click a row
    listeners: {
      'rowdblclick': {
        handler: function(grid, rowIndex) {
          Ext.History.add("products/edit/" + rowIndex);
        }
      }
    }
  });

  this.form = new Ext.form.FormPanel({
    items: ['some', 'form', 'items'],

    //adds a cancel button which redirects back to the grid
    buttons: [
      {
        text: 'cancel',
        handler: function() {
          Ext.History.add("products");
        }
      }
    ]
  });

//any other app startup processing you need to perform
};

MyApp.Application.prototype = {
  initialiseHistory: function() {
    //as above
  },

  /**
   * @param {String} token The url token which has just been navigated to
   * (e.g. if we just went to http://myurl.com/#someurl, token would be 'someurl')
   */
  handleHistoryChange: function(token) {
    var token = token || "";
    switch(token) {
      case 'products':        this.showProductsGrid();     break;
      case 'products/edit/1': this.showProductEditForm(1); break;
      case '':                //nothing after the #, show a default view
    }
  },

  showProductsGrid: function() {
    //some logic to display the grid, depending on how your app is structured
  },

  showProductEditForm: function(product_id) {
    //displays the product edit form for the given product ID.
  }
};

Ext.onReady(function() {
  var app = new MyApp.Application();
});
</code></pre>
<p>So when you visit http://myurl.com/#products, showProductsGrid() will be called automatically, and when you visit http://myurl.com/#products/edit/1, showProductEditForm() will be called with the argument 1.  You can write your own logic here to change tab or show a window or whatever it is you do to show a different view to the user.</p>
<p>I'm not suggesting you parse the url token using a giant switch statement like I have above - this is only an example.  You could get away with something like that for a very small app but for anything bigger you'll probably want some kind of a router.  That goes a little beyond the scope of this article but it is something I will return to at a later date.</p>
<p>There is also an example of Ext.History available on the <a href="http://extjs.com/deploy/dev/examples/history/history.html">Ext samples pages</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Custom containers with ExtJS]]></title>
            <link>https://edspencer.net//2009/1/6/custom-containers-with-extjs</link>
            <guid>custom-containers-with-extjs</guid>
            <pubDate>Tue, 06 Jan 2009 21:42:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>ExtJS has several built-in Container classes - classes which can contain one or more other Ext.Components (such as Grids, Forms, other Panels, etc).  The most obvious example of a Container is the Ext.Panel class, along with its subclasses such as <a href="http://extjs.com/deploy/dev/docs/?class=Ext.TabPanel">Ext.TabPanel</a>, <a href="http://extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel">Ext.form.FormPanel</a> and <a href="http://extjs.com/deploy/dev/docs/?class=Ext.Window">Ext.Window</a>.  With each container class you can add a bunch of components, like this:</p>
<pre><code class="language-javascript">//a child component to be added to the container below
var myComponent = new Ext.Panel({html: 'component 1'});

//Ext.Panel is a subclass of Ext.Container
var myPanel = new Ext.Panel({
  items: [
    myComponent,
    {html: 'component 2'},
    {html: 'component 3'}
  ]
});
</code></pre>
<p>Which will just create a Panel with three other Panels as its child components ('panel' is the default xtype, so we don't have to specify it).  More to the point, you can add and remove components from the Container like this:</p>
<pre><code class="language-javascript">myPanel.add({html: 'component 4'});
myPanel.remove(myComponent);
</code></pre>
<p>As myPanel is an Ext.Container subclass, the methods add() and remove() automatically add or remove child components from within the Container, and take care of any rendering that needs to be performed.  Most of the time this is great, but what if you want to write your own custom Container?  Say you had a bunch of shortcut links which performed some action in your application, and for styling or other reasons you want to put them into markup like this:</p>
<pre><code class="language-xml">&#x3C;div class="x-shortcuts-wrapper">
  &#x3C;div class="x-shortcuts-header">&#x3C;/div>
  &#x3C;div class="x-shortcuts">
    &#x3C;!-- child components to go here -->
  &#x3C;/div>
  &#x3C;div class="x-shortcuts-footer">&#x3C;/div>
  &#x3C;button class="x-shortcuts-add">Add&#x3C;/button>
&#x3C;/div>
</code></pre>
<p>You might write something like this:</p>
<pre><code class="language-javascript">Ext.ns('MyApp');
/**
 * @class MyApp.Shortcuts
 * @extends Ext.Container
 * Container for application shortcuts
 */
MyApp.Shortcuts = Ext.extend(Ext.Container, {
  /**
   * Creates the HTML markup for the shortcuts container
   * @param {Ext.Container} ct The container into which this container will be rendered
   */
  onRender: function(ct) {
    this.el = ct.createChild({
      cls: 'x-shortcuts-wrapper',
      children: [
        {cls: 'x-shortcuts-header'},
        {cls: 'x-shortcuts'},
        {cls: 'x-shortcuts-footer'},
        {cls: 'x-shortcuts-add', tag: 'button'}
      ]
    });
    
    MyApp.Shortcuts.superclass.onRender.apply(this, arguments);
    
    this.shortcutsHolder = this.el.child('.x-shortcuts');
  },
  
  //tells the container which element to add child components into
  getLayoutTarget: function() {
    return this.shortcutsHolder;
  }
});
</code></pre>
<p>So our onRender method is responsible for creating some markup, which must be assigned to this.el.  We're also calling the onRender() function of the superclass (Ext.Container) to make sure nothing is missed out.</p>
<p>The critical elements here are the getLayoutTarget() function, and the last line on onRender().  Usually when you subclass Ext.Container, the add() and remove() functions add and remove from this.el, which would result in something like this:</p>
<pre><code class="language-xml">&#x3C;div class="x-shortcuts-wrapper">
  &#x3C;div class="x-shortcuts-header">&#x3C;/div>
  &#x3C;div class="x-shortcuts">&#x3C;/div>
  &#x3C;div class="x-shortcuts-footer">&#x3C;/div>
  &#x3C;button class="x-shortcuts-add">Add&#x3C;/button>
  &#x3C;!-- child components will end up here -->
&#x3C;/div>
</code></pre>
<p>To prevent this from happening, we obtain a reference to the element we want components to actually be rendered to, and return that with getLayoutTarget().  After that the Container will once again do your bidding.</p>
<p>As of the time of writing getLayoutTarget() is not to be found anywhere in the <a href="http://extjs.com/deploy/dev/docs/">Ext documentation</a> (version 2.2), so my thanks go to Condor and Animal for answering my question on the <a href="http://extjs.com/forum/showthread.php?t=56472">ExtJS forum thread</a>.</p>
<p>To round off the example, say your Shortcut class looked something like this:</p>
<pre><code class="language-javascript">
/**
 * @class MyApp.Shortcut
 * @extends Ext.Component
 * Clickable shortcut class which renders some HTML for a standard application shortcut
 */
MyApp.Shortcut = function(config) {
  var config = config || {};
 
  //apply some defaults
  Ext.applyIf(config, {
    text: 'Shortcut Name',
    icon: 'default_shortcut.gif'
  });
 
  //call the superclass constructor
  MyApp.Shortcut.superclass.constructor.call(this, config);
};
Ext.extend(MyApp.Shortcut, Ext.Component, {
  onRender: function(ct) {
    this.el = ct.createChild({
      cls: 'x-shortcut',
      children: [
        {
          tag: 'img',
          src: this.initialConfig.icon
        },
        {
          tag:  'span',
          html: this.initialConfig.text
        }
      ]
    });
    
    MyApp.Shortcut.superclass.onRender.apply(this, arguments);
  }
});

Ext.reg('shortcut', MyApp.Shortcut);
</code></pre>
<p>Then our container would be created like this:</p>
<pre><code class="language-javascript">
new MyApp.Shortcuts({
  items: [
    new MyApp.Shortcut({text: 'Shortcut 1', icon: 'shatner.gif'}),
    {xtype: 'shortcut', text: 'Shortcut 2', icon: 'nimoy.gif'},
    {xtype: 'shortcut'}
  ]
});
</code></pre>
<p>Which would produce HTML like this:</p>
<pre><code class="language-xml">&#x3C;div class="x-shortcuts-wrapper">
  &#x3C;div class="x-shortcuts-header">&#x3C;/div>
  &#x3C;div class="x-shortcuts">
    &#x3C;div class="x-shortcut">
      &#x3C;img src="shatner.gif" />
      &#x3C;span>Shortcut 1&#x3C;/span>
    &#x3C;/div>
    &#x3C;div class="x-shortcut">
      &#x3C;img src="nimoy.gif" />
      &#x3C;span>Shortcut 2&#x3C;/span>
    &#x3C;/div>
    &#x3C;div class="x-shortcut">
      &#x3C;img src="default_shortcut.gif" />
      &#x3C;span>Shortcut Name&#x3C;/span>
    &#x3C;/div>
  &#x3C;/div>
  &#x3C;div class="x-shortcuts-footer">&#x3C;/div>
  &#x3C;button class="x-shortcuts-add">Add&#x3C;/button>
&#x3C;/div>
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript bra size calculator]]></title>
            <link>https://edspencer.net//2008/11/28/javascript-bra-size-calculator</link>
            <guid>javascript-bra-size-calculator</guid>
            <pubDate>Fri, 28 Nov 2008 12:15:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of the more mesmerizing websites I've worked on recently was for a lingerie boutique in the UK.  Aside from the unenviable task of having to look at pictures of women in lingerie all day, I was also forced (forced!) to write a bra size calculator.</p>
<p>The theory behind bra size calculation is arcane and somewhat magical.  Understanding of it does not come easily to man nor beast, so it is lucky that I, falling cleanly into neither category, have passed through pain and torment to save you the trouble.</p>
<p><a href="http://honeyslingerieboutique.com/fitting">Check it out</a>.</p>
<p>Pleasing, no?  The code looks like this, and can be found <a href="http://honeyslingerieboutique.com/includes/calculator.js">here</a>:</p>
<pre><code class="language-javascript">
var BraCalculator = {
  
  /**
   * The string to be returned when the result could not be calculated.
   */
  unknownString: "Unknown",
  
  cupSizes: ["A", "B", "C", "D", "DD", "E", "EE", "F", "FF", "G", "GG", "H", "HH", 
             "J", "JJ", "K", "KK", "L", "LL", "M", "MM", "N", "NN"],
  
  /**
   * Returns the correct bra size for given under bust and over bust measurements
   * @param {Number} underBust The measurement taken under the bust (in inches)
   * @param {Number} overBust The measurement taken over the bust (in inches)
   * @return {String} The correct bra size for the given measurements (e.g. 32C, 40DD, etc)
   */
  calculateSize: function(underBust, overBust) {
    var bandSize = this.calculateBandSize(underBust);
    var cupSize  = this.calculateCupSize(bandSize, overBust);
    
    if (bandSize &#x26;&#x26; cupSize) {
      return bandSize + cupSize;
    } else {
      return this.unknownString;
    };
  },
  
  /**
   * Calculates the correct band size for a given under bust measurement
   * @param {Number} underBust The measurement under the bust
   * @return {Number} The correct band size
   */
  calculateBandSize: function(underBust) {
    var underBust = parseInt(underBust, 10);
    return underBust + (underBust % 2) + 2;
  },
  
  /**
   * Calculates the Cup size required given the band size and the over bust measurement
   * @param {Number} bandSize The measured band size (should be an even number)
   * @param {Number} overBust The measurement taken over the bust
   * @return {String} The appropriate alphabetical cup size
   */
  calculateCupSize: function(bandSize, overBust) {
    var bandSize = parseInt(bandSize, 10);
    var overBust = parseInt(overBust, 10);
    var diff     = overBust - bandSize;
    
    var result   = this.cupSizes[diff][/diff];
    
    //return false if we couldn't lookup a cup size
    return result ? result : false;
  }
};
</code></pre>
<p>And to apply it to your own pages, use something a bit like this:</p>
<pre><code class="language-javascript">jQuery(document).ready(function(){
  //add listeners to band and cup measurement text boxes
  jQuery('#back').keyup(Honeys.updateBraSizeCalculation);
  jQuery('#cup').keyup(Honeys.updateBraSizeCalculation);
});

var Honeys = {
  updateBraSizeCalculation: function() {
    var back = jQuery('#back')[0].value;
    var cup  = jQuery('#cup')[0].value;
    
    if (back.length > 0 &#x26;&#x26; cup.length > 0) {
      jQuery('#fit')[0].value = BraCalculator.calculateSize(back, cup);
    };
  }
};
</code></pre>
<p>Now we're talking UK sizes here, so exercise extreme caution!  It should be trivial to adapt to your country with our lovely <a href="http://honeyslingerieboutique.com/fitting#charts">conversion charts</a>.</p>
<p>Don't pretend you're not going to play with it.  You know you are.  Like, right now.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Weird bug preventing ExtJS checkboxes from submitting properly]]></title>
            <link>https://edspencer.net//2008/10/24/weird-bug-preventing-extjs-checkboxes</link>
            <guid>weird-bug-preventing-extjs-checkboxes</guid>
            <pubDate>Fri, 24 Oct 2008 13:53:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>This applies to ExtJS 2.2, the most current version as of the time of writing.</p>
<p>Checkboxes often make their way into my Ext JS forms.  Sometimes, though, they don't behave as expected.  Checking and unchecking them would frequently fail, simply not doing anything.  Sometimes it would work, sometimes it wouldn't - how frustrating!</p>
<p>It turns out there is a bug with ticking/unticking checkboxes in Ext.  If you click on the checkbox itself everything works fine - the image of the checkbox updates and the correct value is submitted.  If however you click on the checkbox's <em>label</em>, the image of the checkbox is updated but the correct value is <strong>not</strong> submitted.  So if the box started off unticked and you ticked it by clicking the label, the image is updated but nothing else happens.</p>
<p>This is extremely unintuitive because you can see that the box has been checked, but its internal representation hasn't actually changed.  Because I usually click the label this took me over an hour to track down, so I hope this helps someone out.  Once I had identified the bug, a quick Google search points to <a href="http://extjs.com/forum/showthread.php?t=44603">this thread on the ExtJS forums</a>, which has some guidance on this.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Ext.apply works, and how to avoid a big headache]]></title>
            <link>https://edspencer.net//2008/8/27/how-extapply-works-and-how-to-avoid-big</link>
            <guid>how-extapply-works-and-how-to-avoid-big</guid>
            <pubDate>Wed, 27 Aug 2008 18:25:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ext.apply is one of those magic <a href="http://www.extjs.com">Ext JS</a> methods which copies the essence of one object onto another.  You usually call it like this:</p>
<pre><code class="language-javascript">Ext.apply(receivingObject, sendingObject, defaults)
</code></pre>
<p>Where defaults are optional.  If you supply defaults, Ext.apply actually does this:</p>
<pre><code class="language-javascript">Ext.apply(receivingObject, defaults);
Ext.apply(receivingObject, sendingObject);
</code></pre>
<p>In other words, the order of precedence of the three arguments goes like this: any properties in receivingObject which are also present in defaults will be overwritten by the property in defaults.  After that has happened, any properties which are present receivingObject (after defaults have been applied) and also present in sendingObject will be overwritten by the sendingObject value.  More graphically:</p>
<pre><code class="language-javascript">Ext.apply({a: 'receiver'}, {a: 'sender'}, {a: 'default'}); // = {a: 'sender'}
</code></pre>
<p>For me, this was slightly unexpected as I expected the default options to have the lowest priority - that is the default option would only be copied across if it was not present in either the receiving or the sending objects, so watch out for that.</p>
<p>Anyway that's all well and good once you know how it works inside, but while watching an otherwise excellent screencast from Jay Garcia (see <a href="http://tdg-i.com/42/ext-js-screencast-003-extapply-published">http://tdg-i.com/42/ext-js-screencast-003-extapply-published</a>), something odd happened.  The example he gave went like this (commented lines signify the output returned by Firebug):</p>
<pre><code class="language-javascript">var obj1 = {x: 'x string', y: 'y string'}
// = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// = {a: 'a string', b: 4289, c: function(){}}

var obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})
obj3 
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
obj2
// = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
obj3 === obj2 
// true - obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'fwaifewfaije', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
</code></pre>
<p>So basically he set up obj1 and obj2 with non-conflicting properties, then merged them with some defaults to create obj3.  In this case the defaults didn't conflict with the properties from obj1 or obj2, so obj3 is essentially a straightforward combination of obj1 and obj2, plus a default pxyz value.</p>
<p>What he did then however was to create obj4 as a combination of obj2 and obj3, along with a default value for the 'a' property, which <em>was</em> a property of obj2 and obj3.  Crucially, obj4's 'a' property was set to the default value, which as we've seen from how Ext.apply works above, should never happen (it should be set to the default value but then immediately set back again on the second internal Ext.apply call).</p>
<p>So what gives?  Well, it turns out this is because when calling:</p>
<pre><code class="language-javascript">obj3 = Ext.apply(obj2, obj1, {pxyz: 'soifje'})
</code></pre>
<p>obj3 and obj2 are the exact same object, as Ext.apply returns the first argument after the apply process has taken place.  So in the next call:</p>
<pre><code class="language-javascript">obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
</code></pre>
<p>obj3 and obj2 are in fact both references to the same object, which is causing the unexpected default value.  We can show this by manually creating a new obj3 with the exact same properties, and running the example again:</p>
<pre><code class="language-javascript">var obj1 = {x: 'x string', y: 'y string'}
// obj1 = {x: 'x string', y: 'y string'}

var obj2 = {a: 'a string', b: 4289, c: function(){}}
// obj2 = {a: 'a string', b: 4289, c: function(){}}

var obj3 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj2 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
// obj3 === obj2 => false... obj3 and obj2 are the same object

var obj4 = Ext.apply(obj3, obj2, {a: 'fwaifewfaije'})
// obj4 = {a: 'a string', b: 4289, pxyz: 'soifje', x: 'x string', y: 'y string'}
</code></pre>
<p>This time around we get what we expect - the default value is not applied because it is already present in obj2. Here, obj2 is not the same object as obj3, even though their properties are identical.</p>
<p>I'm not completely certain why two references to the same object cause this behaviour, but the code above does appear to demonstrate that this is what is happening (you can just copy/paste each example into Firebug to reproduce it).</p>
<p>Moral of the story?  Well, now you have a more detailed understanding of Ext.apply, and hopefully you'll be on your guard about referencing the same object by two different variables when performing this type of operation.  I know I will be ;)</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Don't forget the wurst]]></title>
            <link>https://edspencer.net//2008/8/23/dont-forget-wurst</link>
            <guid>dont-forget-wurst</guid>
            <pubDate>Sat, 23 Aug 2008 10:57:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>So it came to be realised during Rails Camp 08 that the world was sadly lacking in William Shatner based list apps.  Thankfully, the <a href="http://railslove.com">Railslove guys</a> (plus <a href="http://playtype.net">Rany</a>) have come to the rescue with <a href="http://dontforgetthewurst.com">don't forget the wurst</a>.  If you're looking for something delightfully random in your life, you may have just found it.</p>
<p>Check out my <a href="http://dontforgetthewurst.com/lists/38-shatners-greatest-hits">Shatner's greatest hits list</a> and Ask William about a few of the items - he will furnish you with compelling and thoughtful answers.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DRYing up your CRUD controller RSpecs]]></title>
            <link>https://edspencer.net//2008/8/20/drying-up-your-crud-controller-rspecs</link>
            <guid>drying-up-your-crud-controller-rspecs</guid>
            <pubDate>Wed, 20 Aug 2008 11:21:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A lot of what we do in Rails boils down to simple Crud.  If you're in the habit of developing admin sections to allow your clients to control the front end of their site, you'll probably have noticed that these controllers in particular tend to all look the same.  There are quite a few ways to DRY up the controller itself - using something like make_resourceful, for example, but what about your RSpec files?</p>
<p>Robby Russell <a href="http://www.robbyonrails.com/articles/2008/08/19/rspec-it-should-behave-like">recently posted</a> a short article about RSpec's Shared Example Groups.  Take a look at his post or at the <a href="http://rspec.info/documentation/">RSpec documentation</a> (it's not scary) to see how they work in more detail, but they basically allow you to share your it "should" do ..... end blocks, enabling them to be reused multiple times.</p>
<p>When you think about it, every time we spec a basic CRUD controller, we're doing the same thing - we should be able to just do something like this:</p>
<pre><code class="language-ruby">require File.dirname(__FILE__) + '/../../spec_helper'

describe Admin::ContactsController do
 before(:each) do
   @model = 'Contact'
   login_as_admin
 end

 it_should_behave_like "CRUD GET index"
 it_should_behave_like "CRUD GET show"
 it_should_behave_like "CRUD POST create"
 it_should_behave_like "CRUD PUT update"
 it_should_behave_like "CRUD DELETE destroy"
 it_should_behave_like "CRUD GET edit"
end
</code></pre>
<p>Well luckily for you we can!  I've been using this pattern with most of my CRUD-based controllers for a while now.  You just set up the model's name at the top (and in the case above perform the login_as_admin helper method to log the user in), and for each action in the controller use a specialised shared example group.  The example groups all know how to understand your @model definition in before(:each) and map it to the various expectations that the CRUD specs run.</p>
<p>The great thing about this approach is that you can just dump these into your spec file and update them later if you need to.  For example if you need to paginate the index action instead of the default find(:all) the shared example group will test, just remove the it_should_behave_like("CRUD GET index") and add your own describe block - the rest of the it_should_behave_like lines can stay as they are.</p>
<p>This approach works especially well if you take the approach to CRUD controllers where you create a CrudController class and subclass your CRUD controllers from it - e.g. from the example above:</p>
<pre><code class="language-ruby">class Admin::ContactsController &#x3C; Admin::CrudController

end
</code></pre>
<p>My Admin::CrudController reflects on the name of the ContactsController subclass here and figures everything out without me having to do any work.  This works because almost all of my Admin section code works the same way.  If I need to diverge from the default CRUD behaviour, I just redefine the particular action in Admin::ContactsController:</p>
<pre><code class="language-ruby">class Admin::ContactsController &#x3C; Admin::CrudController
  def index
    #my alternative implementation
  end
end
</code></pre>
<p>I personally prefer this approach over the more declarative alternatives such as make_resourceful because I feel more in control this way.  That said, I'm open to persuasion :)</p>
<p>Anyway the code for the shared example groups is on Github at http://github.com/edspencer/rspec-crud-controller-shared-example-groups.  There's only one file there - just chuck in in your spec directory and add this line to spec_helper.rb:</p>
<pre><code class="language-ruby">require File.expand_path(File.dirname(__FILE__) + "/crud_controller_matchers")
</code></pre>
<p>Apart from the magic going on in the CrudSetup module at the top of that file, there's nothing special going on here, so you can tweak the examples to your particular approach.  There's a good chance some of that code could be written more cleanly so please feel free to suggest changes / fork the file on Github.</p>
<p>UPDATE - Actually no, ignore the FUD about make_resourceful above, it works remarkably well with very few modifications to the shared groups - I'll post those up as soon as I'm done.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cleaning up an example Ext JS form]]></title>
            <link>https://edspencer.net//2008/8/8/cleaning-up-example-ext-js-form</link>
            <guid>cleaning-up-example-ext-js-form</guid>
            <pubDate>Fri, 08 Aug 2008 21:07:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>One of my recent Ext JS forms had a section which looked like this:</p>
<pre><code class="language-javascript">
items: [
  new Ext.Button({
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win;
      
      if (!win) {
        win = new Ext.Window({
          title: 'Preview Video',
          modal: true,
          height: 377,
          width: 368,
          items: [
            new Ext.Panel({
              autoLoad: '/admin/videos/' + video_id + '/preview.html'
            })
          ],
          buttons: [
            {
              text: 'OK',
              handler: function() {
                win.close();
              }
            }
          ]
        });
        
      };
      win.show();
      
    }
  })
]
</code></pre>
<p>Not horrific but not nice either - let's DRY this up.  It's not too pleasant to read but all it's really doing is rendering a customised Ext.Button which opens up a modal Ext.Window, in which is loaded the contents of a known url.</p>
<p>Ok so let's start with that Window.  First, we'll make a subclass of Ext.Window:</p>
<pre><code class="language-javascript">
/**
 * AdFunded.views.Video.PreviewWindow
 * @extends Ext.Window
 * A simple Preview window for the given video_id
 */
AdFunded.views.Video.PreviewWindow = function(config) {
  var config = config || {};
    
  Ext.applyIf(config, {
    title: 'Preview Video',
    modal: true,
    height: 377,
    width: 368,
    items: [
      new Ext.Panel({
        autoLoad: '/admin/videos/' + config.video_id + '/preview.html'
      })
    ],
    buttons: [
      {
        text: 'OK',
        scope: this,
        handler: function() {
          this.window.close();
        }
      }
    ]
  });
  
  AdFunded.views.Video.PreviewWindow.superclass.constructor.call(this, config);
  
  this.window = this;
};
Ext.extend(AdFunded.views.Video.PreviewWindow, Ext.Window);
Ext.reg('video_preview_window', AdFunded.views.Video.PreviewWindow);
</code></pre>
<p>Note the namespacing employed above - within an Ext MVC framework I have been developing across several projects for the last few months, all views follow this structure.  AdFunded is the name of the application.  The precise structure doesn't matter here, but using a namespace for each app does.</p>
<p>So we've taken the Window setup out of our view now, which leaves us with:</p>
<pre><code class="language-javascript">
items: [
  new Ext.Button({
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win;
      
      if (!win) {
        win = new AdFunded.views.Video.PreviewWindow({video_id: id});
      };
      win.show();
      
    }
  })
]
</code></pre>
<p>Great - we've gone from 34 lines in our view to 15, and scored ourselves a reusable Window component which we can call from anywhere in the app.  Nice work, but there's more to come... If we're going to use the Preview Window again, we'll probably need to use that Preview Button again too.  Let's see:</p>
<pre><code class="language-javascript">
/**
 * AdFunded.views.Video.PreviewButton
 * @extends Ext.Button
 * Displays a Preview Window for the given video_id
 */
AdFunded.views.Video.PreviewButton = function(config) {
  var config = config || {};
  
  Ext.applyIf(config, {
    text: 'Preview Video',
    iconCls: 'play',
    handler: function() {
      var win = new AdFunded.views.Video.PreviewWindow({video_id: config.video_id});
      win.show();
    }
  });
  
  AdFunded.views.Video.PreviewButton.superclass.constructor.call(this, config);
};
Ext.extend(AdFunded.views.Video.PreviewButton, Ext.Button);
Ext.reg('video_preview_button', AdFunded.views.Video.PreviewButton);
</code></pre>
<p>Which leaves us with the following the the view:</p>
<pre><code class="language-javascript">items: [
  {
    xtype: 'video_preview_button',
    video_id: id
  }
]
</code></pre>
<p>We've now gone from 34 lines to 6 (in the view at least), but the point is not about cutting out lines of code - it's creating reusable components.  We've added 20 lines overall this way but we now have two extra components that we can call on at any time (with minimal lines of code), safe in the knowledge that they will provide a consistent experience each time.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ExtJS Radio Buttons and Square Brackets]]></title>
            <link>https://edspencer.net//2008/8/8/extjs-radio-buttons-and-square-brackets</link>
            <guid>extjs-radio-buttons-and-square-brackets</guid>
            <pubDate>Fri, 08 Aug 2008 20:41:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>While creating an ExtJS form with several radio buttons today I ran into a bug which caused none of them to work as expected, even though there were no errors/exceptions.  To cut a long story short, it was because I was setting the name to "schedule[include_type]" - like this:</p>
<pre><code class="language-javascript">{
  xtype: 'radio',
  name: 'schedule[include_type]',
  inputValue: 'page',
  boxLabel: 'Show page:'
}
</code></pre>
<p>This radio button is one of 4, which allows the user which type of file they want to include on a particular model (a Schedule in this case) - be it Page, Video, Category or one other.  The thing is - none of them work with the square brackets in the name.  If you remove the brackets, they all work correctly, but the server-side is relying on those brackets to be present to group the data correctly.</p>
<p>In the end I bit the bullet and updated my submit method to add a new parameter directly - here's a full example:</p>
<pre><code class="language-javascript">form = new Ext.form.FormPanel({
  items: [
    {
      xtype: 'radio',
      name: 'include_type',
      inputValue: 'page',
      boxLabel: 'Show page:'
    },
    {
      xtype: 'radio',
      name: 'include_type',
      inputValue: 'category',
      boxLabel: 'Show category:'
    },
    ... plus some extra items
  ],
  buttons: [
    {
      text: 'Save',
      handler: function() {
        
        //find the currently selected include_type from the form
        var include_type = this.form.getValues()['include_type'];
        
        //note the params option - this needs to be added manually otherwhise 
        //schedule[include_type] won't appear
        form.form.submit({
          waitMsg: 'Saving Data...',
          params: "schedule[include_type]=" + include_type,
          url: some url...
        });
      }
    }
  ]
})
</code></pre>
<p>Note: I don't usually add buttons in the way above so I'm not sure if the form.form.submit will work correctly here - see <a href="http://extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel">http://extjs.com/deploy/dev/docs/?class=Ext.form.FormPanel</a> for information about overriding submit.</p>
<p>So what we're doing here is finding which radio button is currently checked, and appending this under "schedule[include_type]" when POSTing the form variables to the server.  This really isn't pleasant but seems to be the best way around this limitation for now.</p>
<p>I regularly use square brackets in other Ext JS Fields - Radio Buttons seem to be the only ones that have this problem.  <a href="http://extjs.com/forum/showthread.php?p=185296">http://extjs.com/forum/showthread.php?p=185296</a> has a bit of background behind this, but no real solution.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When Git tells you it failed to push some refs]]></title>
            <link>https://edspencer.net//2008/4/25/when-git-tells-you-it-failed-to-push</link>
            <guid>when-git-tells-you-it-failed-to-push</guid>
            <pubDate>Fri, 25 Apr 2008 14:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I received an unhelpful error while trying to push to a repository on Github today:</p>
<pre><code class="language-bash">git push
To git@github.com:user/repo.git
! [rejected] branchname -> branchname (non-fast forward)
error: failed to push some refs to 'git@github.com:user/repo.git'
</code></pre>
<p>In case you ever have the same problem, all you have to do is a quick git pull first, then you can carry on as normal.  Easy when you know how...</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Git clone vs Git submodule]]></title>
            <link>https://edspencer.net//2008/4/17/git-clone-vs-git-submodule</link>
            <guid>git-clone-vs-git-submodule</guid>
            <pubDate>Thu, 17 Apr 2008 12:01:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Having recently made the switch from svn to git, I wanted to achieve what svn externals did (and what Piston did better).  Turns out this is pretty simple, for example to get rails on edge:</p>
<pre><code>cd your_git_dir
git submodule add git://github.com/rails/rails.git vendor/rails
</code></pre>
<p>A couple of other default submodules you'll want:</p>
<pre><code>git submodule add git://github.com/dchelimsky/rspec.git vendorpluginsrspec
git submodule add git://github.com/dchelimsky/rspec-rails.git vendorpluginsrspec-rails
</code></pre>
<p>What submodule does is to check out the submodules as their own repositories, so they are tracked independently of the repository you made them submodules of.  The submodules you have are tracked in the .gitmodules file, which might look something like this:</p>
<pre><code>[submodule "vendorrails"]
 path = vendor/rails
 url = git://github.com/rails/rails.git
[submodule "vendor/plugins/rspec"]
 path = vendor/plugins/rspec
 url = git://github.com/dchelimsky/rspec.git
[submodule "vendor/plugins/rspec-rails"]
 path = vendor/plugins/rspec-rails
 url = git://github.com/dchelimsky/rspec-rails.git
</code></pre>
<p>Or at least that's how it should look, Windows seems to mess this up into looking something like the following:</p>
<pre><code>[submodule "vendorrails"]
 path = vendor\rails
[submodule "vendorrails"]
 url = git://github.com/rails/rails.git
[submodule "vendorpluginsrspec"]
 path = vendor\plugins\rspec
[submodule "vendorpluginsrspec"]
 url = git://github.com/dchelimsky/rspec.git
[submodule "vendorpluginsrspec-rails"]
 path = vendor\plugins\rspec-rails
[submodule "vendorpluginsrspec-rails"]
 url = git://github.com/dchelimsky/rspec-rails.git
</code></pre>
<p>Note especially that you need to remove the 's and replace all 's with /'s. If you don't git will give a fail message like:</p>
<pre><code>fatal: bad config file line 2 in .gitmodules
No submodule mapping found in .gitmodules for path 'vendor/plugins/attachment_fu'
</code></pre>
<p>I don't know why it's doing that, maybe it's something I'm doing wrong but you'll need to tidy it up to make it look more like the first example in order for it to work properly.</p>
<p>One final thing to be aware of is that when you clone onto a new machine you'll need to run the following commands:</p>
<pre><code>git submodule init
git submodule update
</code></pre>
<p>This will initialise the submodules that are referenced in the .gitmodules file, then pull them down.  By default cloning doesn't seem to do that.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Useful Rails javascript expansions for EXTJS]]></title>
            <link>https://edspencer.net//2008/4/16/useful-rails-javascript-expansions-for</link>
            <guid>useful-rails-javascript-expansions-for</guid>
            <pubDate>Wed, 16 Apr 2008 18:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you're using Edge Rails (or > 2.1, which isn't out at time of writing), and are using the EXT JS framework anywhere, here are a couple of handy javascript include tag expansions to clean up your views.  Just chuck them into any file in your config/initializers directory:</p>
<pre><code class="language-ruby">ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext => ['ext/adapter/ext/ext-base', 'ext/ext-all']

ActionView::Helpers::AssetTagHelper.register_javascript_expansion :ext_grid_filter => ['ext/ux/menu/EditableItem', 'ext/ux/menu/RangeMenu', 'ext/ux/grid/GridFilters', 'ext/ux/grid/filter/Filter', 'ext/ux/grid/filter/StringFilter', 'ext/ux/grid/filter/DateFilter', 'ext/ux/grid/filter/ListFilter', 'ext/ux/grid/filter/NumericFilter', 'ext/ux/grid/filter/BooleanFilter']
</code></pre>
<p>The top one includes the relevant EXT base files and the second one includes all the Grid Filters from the excellent Filter Grid plugin (see <a href="http://ccinct.com/lab/filter-grid/">http://ccinct.com/lab/filter-grid/</a>.</p>
<p>Include them as usual like this:</p>
<pre><code class="language-ruby">javascript_include_tag :ext, :ext_grid_filter, :cache => 'ext_javascripts'
</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EXT remote-loading forms with Combo boxes]]></title>
            <link>https://edspencer.net//2008/4/16/ext-remote-loading-forms-with-combo</link>
            <guid>ext-remote-loading-forms-with-combo</guid>
            <pubDate>Wed, 16 Apr 2008 15:41:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Something that's harder than it should be is populating an EXT edit form with form data, where one of the form fields is a select box.  If there is a specific set of values that this select can take, then you can hard code that using a SimpleStore, like this:</p>
<pre><code class="language-javascript">
var exampledata = [['AL', 'Alabama'],
                   ['AK', 'Alaska'],
                   ['AZ', 'Arizona'],
                   ['AR', 'Arkansas'],
                   ['CA', 'California']];

var store = new Ext.data.SimpleStore({
    fields: ['abbr', 'state'],
    data : exampleData
});

var combo = new Ext.form.ComboBox({
    store: store,
    displayField: 'state',
    valueField: 'abbr',
    ... etc ...
});

form = new Ext.form({
  items: [combo],
  ... etc ...
});

form.load(url_to_load_from);
</code></pre>
<p>So that will populate the select box with the static values you've defined (the 5 states above), then when the form loads it will select the appropriate option automatically.</p>
<p>So far so good, but what if you need to load what goes into the select box dynamically?  Well, first you'll need to set up your remote data store (my server is sending back JSON data, hence the JsonReader):</p>
<pre><code class="language-javascript">
store = new Ext.data.Store({
  url: 'url_to_load_combo_data_from',
  reader: new Ext.data.JsonReader(
    {root: 'states',totalProperty: 'results'},
    [
      {name: 'name', type: 'string', mapping: 'state.name'},
      {name: 'abbr', type: 'string', mapping: 'state.abbr'}
    ]
  )
});
</code></pre>
<p>This will consume data like this:</p>
<pre><code class="language-javascript">
{
  "states": [
    {"state": {"name": "Alabama", "abbr": "AL"}}, 
    {"state": {"name": "Alaska",  "abbr": "AK"}}
  ], 
  "results": "2"
}
</code></pre>
<p>And populate the store with a collection of state records which can then be loaded into the combobox.</p>
<p>Then all you need to do is load the store before loading the form data, and your comboboxes will be correctly populated, displaying the correct option.  Here's the full example:</p>
<pre><code class="language-javascript">store = new Ext.data.Store({
  url: 'url_to_load_combo_data_from',
  reader: new Ext.data.JsonReader(
    {root: 'states',totalProperty: 'results'},
    [
      {name: 'name', type: 'string', mapping: 'state.name'},
      {name: 'abbr', type: 'string', mapping: 'state.abbr'}
    ]
  )
});

var combo = new Ext.form.ComboBox({
    store: store,
    displayField: 'state',
    valueField: 'abbr'
    ... etc ...
});

form = new Ext.formPanel({
  items: [combo]
});

store.load();
form.load(your_form_data_url);
</code></pre>
<p>Be wary using the pagination options on the combobox here (see <a href="http://extjs.com/deploy/dev/docs/?class=Ext.form.ComboBox">http://extjs.com/deploy/dev/docs/?class=Ext.form.ComboBox</a>) - the reason being if your state's 'abbr' features on the second page of the results it won't populate the correct options into the combo box.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Getting EXT PagingToolbars to save state]]></title>
            <link>https://edspencer.net//2008/4/12/getting-ext-pagingtoolbars-to-save</link>
            <guid>getting-ext-pagingtoolbars-to-save</guid>
            <pubDate>Sat, 12 Apr 2008 17:18:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A problem that has recently had me pulling my hair out is how to save state in an EXT PagingToolbar.</p>
<p>Ext makes it easy to save the state of most of its components - by default it does this by setting a cookie with the relevant configuration info, then just reading it back when you load the component again.  I've been using it to save the state of a few EXT grids I've been using on a recent project, this saves config such as which columns you have visible, which column you're sorting by, and how the columns are ordered.</p>
<p>That works great, and is trivial to implement - just set your provider (see <a href="http://extjs.com/deploy/dev/docs/?class=Ext.state.CookieProvider">http://extjs.com/deploy/dev/docs/?class=Ext.state.CookieProvider</a>) and be sure to give your grid an id in its config - this is used as the key in the state provider and needs to be unique for each component.</p>
<p>The problem comes when you're using a paging toolbar though, as this does not save state, so every time you view the grid you're back to page 1.  You can add state behaviour to the paginator by piggybacking the grid's state store, here's how it's done:</p>
<pre><code class="language-javascript">
Ext.PagingToolbar.override({
  init : function (grid) {
    this.grid = grid;        
    this.grid.on("beforestatesave", this.saveState, this);    
    Ext.util.Observable.capture(grid.store, this.onStateChange, this);
  },
  saveState : function(grid, state) {
    state.start = grid.store.lastOptions.params.start;
  },
  onStateChange : function(ev, store, records, options) {
    if (ev == "load") {this.grid.saveState(); };
  }
});
</code></pre>
<p>Basically we're intercepting the attached Grid's saveState() event and appending the current start value as stored in the Grid's DataStore (e.g. if you're looking at page 3 with 25 rows per page then start = 50).  If you examine the contents of your state provider using Firebug (Ext.state.Manager.getProvider().state, then look for the key that matches the id of your grid), you'll see that there is now a record for 'start', which grabbed the correct value from the Grid's store.</p>
<p>All you need to do then is retrieve that value from the state provider and load your store accordingly:</p>
<pre><code class="language-javascript">
store = new Ext.data.Store({... your store config ...});

grid = new Ext.grid.GridPanel({
  id: 'unique_grid_id',
  store: store,
  ... other grid config ...
});

//shorthand way of retrieving state information
var state = Ext.state.Manager.getProvider();

var start = state.get(options.id).start || 0);
store.load({params: {start: start, limit: 25}});
</code></pre>
<p>If the start value for this grid has never been set it'll default to zero - e.g. the first page.  Next time you come back to this grid it'll take you right back to where you were, including all column setup and sorting behaviour you have specified.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rails asset tag expansions]]></title>
            <link>https://edspencer.net//2008/4/12/rails-asset-tag-expansions</link>
            <guid>rails-asset-tag-expansions</guid>
            <pubDate>Sat, 12 Apr 2008 12:39:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you're using edge rails you may have noticed that you can now define your own JavaScript expansions (if you're not on edge this will be included in the imminent 2.1 release).  The default expansion that comes with rails looks like this:</p>
<pre><code>javascript_include_tag :defaults
</code></pre>
<p>Which grabs application.js as well as the prototype/scriptaculous javascripts and includes them all (only do that if you need it all - it adds ~150kb to your page).  But say you've got a line which looks like this:</p>
<pre><code>javascript_include_tag 'my_js_file', 'another_js_file', 'and_another'
</code></pre>
<p>And say you want to include the same set of files on a different page - it turns out Rails makes it really easy to DRY this up.  Make a new file in the config/initializers directory (I call my asset_tag_expansions.rb) and add a line like the following (don't forget to restart your server afterwards):</p>
<pre><code>ActionView::Helpers::AssetTagHelper.register_javascript_expansion :my_js=> ['my_js_file', 'another_js_file', 'and_another']
</code></pre>
<p>Now in your views you can simply put:</p>
<pre><code>javascript_include_tag :my_js
</code></pre>
<p>You can of course register as many of these as you like, and include as many of your own expansions on the same javascript_include_tag line as you want, e.g.:</p>
<pre><code>javascript_include_tag :my_js, :another_expansion, :and_another
</code></pre>
<p>The same applies to stylesheets:</p>
<pre><code>ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :public_styles=> ['reset', 'layout', 'home']
  stylesheet_link_tag :public_styles
</code></pre>
<p>Finally, although you're getting all that onto one line, each asset file is still being requested separately by your browser, each time making another nasty expensive HTTP request.  Rectify that:</p>
<pre><code>stylesheet_link_tag :public_styles, :cache => 'public'
  javascript_include_tag :my_js, :another_expansion, :and_another, :cache => 'public'
</code></pre>
<p>This bundles up your three stylesheets and concatenates them into a single file, which is called public.css in this case.  In the example above this means two less trips to the server to retrieve the stylesheet files, therefore a faster loading page.  This is helpful because it enables you to maintain small, targeted stylesheets in development which makes finding the relevant CSS declarations easier, without the performance hit of all those HTTP requests when in production.</p>
<p>One final option is to use the :all expansion, which just grabs everything in the stylesheets or javascripts directory.  Be careful with that though as you've got to be sure assets are being loaded in the right order (especially for JavaScript), and that you really need all that asset weight on each page.</p>]]></content:encoded>
        </item>
    </channel>
</rss>