<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Owain Williams</title>
    <description>The latest articles on DEV Community by Owain Williams (@owaincodes).</description>
    <link>https://dev.to/owaincodes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F65179%2F6d4edfad-60c3-431a-a35f-885393a83259.jpg</url>
      <title>DEV Community: Owain Williams</title>
      <link>https://dev.to/owaincodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/owaincodes"/>
    <language>en</language>
    <item>
      <title>Umbraco Features with Claude Worktrees</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Thu, 29 Oct 2026 17:26:42 +0000</pubDate>
      <link>https://dev.to/owaincodes/umbraco-features-with-claude-worktrees-5b3e</link>
      <guid>https://dev.to/owaincodes/umbraco-features-with-claude-worktrees-5b3e</guid>
      <description>&lt;h1&gt;
  
  
  One database per git worktree: building Umbraco features in parallel with Claude Code
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; I run several Claude Code sessions against the same Umbraco 17 site at once, one feature per session. Claude Code gives each session its own git worktree, so the code is isolated. The database isn't, and uSync happily imports whichever branch started last. So I wrote a PowerShell hook that copies the dev database for every new worktree, points the worktree at that copy, keeps media working, picks ports that don't clash, and drops the lot when the worktree goes. It's about twenty seconds of SQL Server work on session start and then you never think about it again. Install steps are at the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;Why not just spin up a fresh database?&lt;/li&gt;
&lt;li&gt;Hooking into Claude Code&lt;/li&gt;
&lt;li&gt;What happens on SessionStart&lt;/li&gt;
&lt;li&gt;What happens on WorktreeRemove&lt;/li&gt;
&lt;li&gt;Things that bit me&lt;/li&gt;
&lt;li&gt;What it doesn't do&lt;/li&gt;
&lt;li&gt;Would it work on your project?&lt;/li&gt;
&lt;li&gt;How to install it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a id="the-problem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I've got into the habit of running a few Claude Code sessions at the same time, each one working on a different feature of the same Umbraco site. It's brilliant. One session is building a new block, another is fixing a bug in a controller, and I'm sat in the middle reviewing diffs with a coffee.&lt;/p&gt;

&lt;p&gt;Claude Code makes this possible with git worktrees. A worktree is a second checkout of the same repo in a different folder, on its own branch. Two sessions can edit, build and commit without ever touching each other's files. Perfect.&lt;/p&gt;

&lt;p&gt;Except an Umbraco site isn't just files.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;appsettings.Local.json&lt;/code&gt; points at one SQL Server database, and the project has uSync set up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"uSync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ImportAtStartup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"All"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means every time you start the site from a worktree, uSync imports &lt;em&gt;that branch's&lt;/em&gt; document types, data types and compositions into the shared database. Start the site from worktree A, then from worktree B, and B's schema stomps all over A's. Start A again and it stomps right back. The backoffice flips between two versions of the content model and neither session can trust what it's looking at.&lt;/p&gt;

&lt;p&gt;Ports are a second, smaller headache. &lt;code&gt;launchSettings.json&lt;/code&gt; pins the site to &lt;code&gt;https://localhost:44373&lt;/code&gt;, so two worktrees can't even run at the same time. Not ideal when the whole point is running things in parallel.&lt;/p&gt;

&lt;p&gt;&lt;a id="why-not-fresh"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just spin up a fresh database?
&lt;/h2&gt;

&lt;p&gt;My first thought was an empty database per worktree and let Umbraco's unattended install do the rest. That falls over almost immediately on this site. uSync here only exports schema (content types, data types, languages, media types), not content, and &lt;code&gt;wwwroot/media&lt;/code&gt; is gitignored. So a fresh install gives you a beautifully empty site with nothing to click on. Not much use for testing a feature.&lt;/p&gt;

&lt;p&gt;What you actually want is a &lt;em&gt;copy&lt;/em&gt; of the main dev database with the branch's schema applied on top. uSync does the second half for free at startup. The first half is a &lt;code&gt;BACKUP ... WITH COPY_ONLY&lt;/code&gt; followed by a &lt;code&gt;RESTORE ... WITH MOVE&lt;/code&gt;, which on a dev sized database takes about twenty seconds. I can live with twenty seconds.&lt;/p&gt;

&lt;p&gt;&lt;a id="hooks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooking into Claude Code
&lt;/h2&gt;

&lt;p&gt;Claude Code has lifecycle hooks, configured in &lt;code&gt;.claude/settings.json&lt;/code&gt;. That file is committed, so once it's in the repo the whole team gets the hooks for free. Two events do everything I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SessionStart&lt;/code&gt;&lt;/strong&gt; fires when a session starts, resumes or compacts. Its &lt;code&gt;cwd&lt;/code&gt; is the worktree root. This is where the database gets provisioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;WorktreeRemove&lt;/code&gt;&lt;/strong&gt; fires when Claude removes a worktree. This is where the database gets dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no hook for "pull request created", and honestly I didn't want one. After the PR goes up you still need the site running to deal with review comments. The lifetime of the worktree is the right lifetime for the database.&lt;/p&gt;

&lt;p&gt;Both events run the same PowerShell script. It reads the event JSON from stdin and branches on &lt;code&gt;hook_event_name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell -NoProfile -ExecutionPolicy Bypass -File &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$env:CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.claude&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;hooks&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;worktree-db.ps1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"WorktreeRemove"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell -NoProfile -ExecutionPolicy Bypass -File &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$env:CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.claude&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;hooks&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;worktree-db.ps1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a id="session-start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens on SessionStart
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Work out if this is a worktree, and where the main checkout lives.&lt;/strong&gt; Git will tell you both. In a worktree, &lt;code&gt;--git-dir&lt;/code&gt; is &lt;code&gt;&amp;lt;main&amp;gt;/.git/worktrees/&amp;lt;name&amp;gt;&lt;/code&gt; while &lt;code&gt;--git-common-dir&lt;/code&gt; is &lt;code&gt;&amp;lt;main&amp;gt;/.git&lt;/code&gt;. If they're the same, we're in the main checkout and there's nothing to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$gitDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--path-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--git-dir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$common&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rev-parse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--path-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--git-common-dir&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$isWorktree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gitDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$common&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$mainRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Split-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Parent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$common&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Read the connection string from the main checkout's &lt;code&gt;appsettings.Local.json&lt;/code&gt;.&lt;/strong&gt; That file is gitignored and specific to each machine, which is exactly why the script reads it rather than hardcoding a server name. Server, database and credentials all come from there, so nothing in the committed script is tied to my laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sweep.&lt;/strong&gt; Before provisioning anything, list every database named &lt;code&gt;&amp;lt;db&amp;gt;_wt_*&lt;/code&gt; and compare it against &lt;code&gt;git worktree list&lt;/code&gt;. Anything without a matching worktree gets dropped. This runs on every session, including ones in the main checkout, so worktrees that were removed by hand or by the desktop app still get tidied up eventually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Copy the database.&lt;/strong&gt; The target name comes from the worktree folder, so a worktree at &lt;code&gt;.claude/worktrees/multi-feature-workflow-c09980&lt;/code&gt; gets a database called &lt;code&gt;tgp_wt_multi_feature_workflow_c09980&lt;/code&gt;. The copy uses SQL Server's own default paths, which the server reports through &lt;code&gt;SERVERPROPERTY&lt;/code&gt;, so it works against a remote instance just as well as a local one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;BACKUP&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tgp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;DISK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;InstanceDefaultBackupPath&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;gp_worktree_seed.bak'&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;COPY_ONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FORMAT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;RESTORE&lt;/span&gt; &lt;span class="n"&gt;FILELISTONLY&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DISK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- to get the logical file names&lt;/span&gt;

&lt;span class="n"&gt;RESTORE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tgp_wt_&lt;/span&gt;&lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;DISK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'...'&lt;/span&gt;
  &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;MOVE&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'tgp'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;InstanceDefaultDataPath&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;gp_wt_....mdf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MOVE&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'tgp_log'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;InstanceDefaultLogPath&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;gp_wt_..._log.ldf'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RECOVERY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;COPY_ONLY&lt;/code&gt; matters here. It doesn't disturb the backup chain if your dev database happens to be in full recovery mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Write the worktree's own &lt;code&gt;appsettings.Local.json&lt;/code&gt;.&lt;/strong&gt; It's a copy of the main one with the database name swapped out, plus one extra Umbraco setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Umbraco"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CMS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Global"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"UmbracoMediaPhysicalRootPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;TGP&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;TGP&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;wwwroot&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;media"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Media files aren't in git, so without this the worktree would show broken images everywhere. &lt;code&gt;UmbracoMediaPhysicalRootPath&lt;/code&gt; points Umbraco at the main checkout's media folder instead. I'd originally planned a directory junction for this and bottled it. &lt;code&gt;git worktree remove --force&lt;/code&gt; deletes the worktree directory, and I really didn't fancy finding out whether it follows junctions into the real media folder. A config setting can't delete anything. Much safer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Pick some ports and tell Claude.&lt;/strong&gt; A hash of the worktree name gives a stable pair of ports in the 45000 range. The script checks they're free and saves them into the generated config, so later sessions print the same pair even while the site is running. Then it writes one line to stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worktree database 'tgp_wt_multi_feature_workflow_c09980' on INI-WIN-066 is ready
(copy of 'tgp'; media served from the main checkout). Run the site from TGP with:
dotnet run --urls "https://localhost:45980;http://localhost:45981"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever a &lt;code&gt;SessionStart&lt;/code&gt; hook prints to stdout gets added to Claude's context. So every session starts already knowing which database it owns and how to run the site. Nobody has to type anything.&lt;/p&gt;

&lt;p&gt;&lt;a id="worktree-remove"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens on WorktreeRemove
&lt;/h2&gt;

&lt;p&gt;Drop the database, then remove the worktree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tgp_wt_&lt;/span&gt;&lt;span class="p"&gt;...]&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;SINGLE_USER&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="k"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tgp_wt_&lt;/span&gt;&lt;span class="p"&gt;...];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$mainRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;worktree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$worktreePath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second step caught me out. Registering a &lt;code&gt;WorktreeRemove&lt;/code&gt; hook &lt;em&gt;replaces&lt;/em&gt; Claude Code's default removal. So if your script only drops the database, the worktree folder just sits there. The hook has to do the &lt;code&gt;git worktree remove&lt;/code&gt; itself, and exit with an error code if the folder is still around afterwards.&lt;/p&gt;

&lt;p&gt;&lt;a id="things-that-bit-me"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bit me
&lt;/h2&gt;

&lt;p&gt;I'd love to say this all worked first time. It did not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$result[0]&lt;/code&gt; on a single row from sqlcmd.&lt;/strong&gt; PowerShell unrolls an array with one element into a plain string, and &lt;code&gt;[0]&lt;/code&gt; on a string gives you its first character. My backup path became &lt;code&gt;C&lt;/code&gt;, which SQL Server helpfully resolved relative to its default backup folder as &lt;code&gt;...\Backup\C\tgp_worktree_seed.bak&lt;/code&gt;. Wrap it in &lt;code&gt;@(...)[0]&lt;/code&gt; and move on with your life.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$ErrorActionPreference = 'Stop'&lt;/code&gt; and native commands.&lt;/strong&gt; In Windows PowerShell 5.1, a native exe writing anything to stderr under &lt;code&gt;Stop&lt;/code&gt; becomes a terminating error. The script runs with &lt;code&gt;Continue&lt;/code&gt; and checks &lt;code&gt;$LASTEXITCODE&lt;/code&gt; explicitly instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$env:CLAUDE_PROJECT_DIR&lt;/code&gt; is the main checkout, not the worktree.&lt;/strong&gt; Use it to find the script. Use the event's &lt;code&gt;cwd&lt;/code&gt; for the worktree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The port probe sees your own site.&lt;/strong&gt; On a resumed session the hook ran while the worktree's site was already up, saw its own ports as busy, and cheerfully printed a different pair. Hence persisting the ports in the generated config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builds touch tracked files.&lt;/strong&gt; Umbraco regenerates &lt;code&gt;umbraco-package-schema.json&lt;/code&gt; on every build. Not a hook problem, but it shows up in &lt;code&gt;git status&lt;/code&gt; after you've tested, so keep an eye out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="what-it-doesnt-do"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's Windows PowerShell plus &lt;code&gt;sqlcmd&lt;/code&gt;. No bash version yet. Sorry Mac folks.&lt;/li&gt;
&lt;li&gt;It needs a SQL Server you can &lt;code&gt;BACKUP&lt;/code&gt; and &lt;code&gt;RESTORE&lt;/code&gt; against: a local instance, Express, LocalDB or a dev server. Azure SQL has no &lt;code&gt;BACKUP DATABASE&lt;/code&gt;, so that's out.&lt;/li&gt;
&lt;li&gt;It assumes one worktree runs one site. Two sites in the same worktree would still collide.&lt;/li&gt;
&lt;li&gt;The desktop app's own worktree cleanup may not fire &lt;code&gt;WorktreeRemove&lt;/code&gt;. The sweep on the next session start covers it, so the worst case is a stale database hanging around for a few hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a id="your-project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Would it work on your project?
&lt;/h2&gt;

&lt;p&gt;Yes, if you're on Windows with a SQL Server you can back up. All the bits that are specific to my project are parameters with sensible defaults. Another Umbraco site needs at most the web project folder name. A .NET site that isn't Umbraco adds the connection string name and switches the media setting off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell -NoProfile -ExecutionPolicy Bypass -File &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$env:CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.claude&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;hooks&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;worktree-db.ps1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -WebProject MySite -ConnectionStringName DefaultConnection -NoMediaSetting"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else (server, credentials, source database) is read from your own local config at runtime. You can also run the script by hand with &lt;code&gt;-Cwd &amp;lt;worktree&amp;gt;&lt;/code&gt; or &lt;code&gt;-Remove &amp;lt;worktree&amp;gt;&lt;/code&gt; to try it outside Claude Code.&lt;/p&gt;

&lt;p&gt;It took about an hour to build, and the combination is what makes it worth it. A copy of the real database. The branch's schema applied by uSync on first run. Media still visible. Ports that don't clash. The whole thing torn down when the worktree goes. Parallel sessions stop being something you manage and become something that just happens.&lt;/p&gt;

&lt;p&gt;&lt;a id="install"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install it
&lt;/h2&gt;

&lt;p&gt;Here's the step by step. This assumes Windows, Windows PowerShell 5.1, and Claude Code with worktrees turned on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Check the prerequisites.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A SQL Server you can &lt;code&gt;BACKUP&lt;/code&gt; and &lt;code&gt;RESTORE&lt;/code&gt; against (local instance, Express, LocalDB or a dev server).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sqlcmd&lt;/code&gt; on your PATH. Open a terminal and run &lt;code&gt;sqlcmd -?&lt;/code&gt; to make sure it's there.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;appsettings.Local.json&lt;/code&gt; in your main checkout with a working connection string. The script reads this, so if the site already runs from your main checkout you're good.&lt;/li&gt;
&lt;li&gt;If it's an Umbraco site, uSync with &lt;code&gt;ImportAtStartup&lt;/code&gt; turned on, otherwise the branch's schema won't land in the copied database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Grab the script.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TODO: link to the script (gist or repo).&lt;/p&gt;

&lt;p&gt;Drop it into your repo at &lt;code&gt;.claude/hooks/worktree-db.ps1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Register the hooks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;.claude/settings.json&lt;/code&gt; (create it if it doesn't exist) and add the &lt;code&gt;SessionStart&lt;/code&gt; and &lt;code&gt;WorktreeRemove&lt;/code&gt; entries from the hooks section above. Both point at the same script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Adjust the parameters if you need to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The defaults assume an Umbraco site with the standard Umbraco connection string name. If your web project folder has a different name, add &lt;code&gt;-WebProject YourFolder&lt;/code&gt; to the command. If it's not an Umbraco site, add &lt;code&gt;-ConnectionStringName YourConnectionString -NoMediaSetting&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Test it by hand first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a worktree and run the script against it directly, so you're not debugging through Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add .claude/worktrees/test-db &lt;span class="nt"&gt;-b&lt;/span&gt; test-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;powershell &lt;span class="nt"&gt;-NoProfile&lt;/span&gt; &lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt; Bypass &lt;span class="nt"&gt;-File&lt;/span&gt; .claude&lt;span class="se"&gt;\h&lt;/span&gt;ooks&lt;span class="se"&gt;\w&lt;/span&gt;orktree-db.ps1 &lt;span class="nt"&gt;-Cwd&lt;/span&gt; .claude&lt;span class="se"&gt;\w&lt;/span&gt;orktrees&lt;span class="se"&gt;\t&lt;/span&gt;est-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the "Worktree database ... is ready" line and a new &lt;code&gt;&amp;lt;db&amp;gt;_wt_test_db&lt;/code&gt; database on your server. Then tear it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;powershell &lt;span class="nt"&gt;-NoProfile&lt;/span&gt; &lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt; Bypass &lt;span class="nt"&gt;-File&lt;/span&gt; .claude&lt;span class="se"&gt;\h&lt;/span&gt;ooks&lt;span class="se"&gt;\w&lt;/span&gt;orktree-db.ps1 &lt;span class="nt"&gt;-Remove&lt;/span&gt; .claude&lt;span class="se"&gt;\w&lt;/span&gt;orktrees&lt;span class="se"&gt;\t&lt;/span&gt;est-db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database and the worktree folder should both be gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Start a Claude Code session in a worktree.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the project in Claude Code with worktrees enabled and start a new session. The first thing in Claude's context should be the "Worktree database ... is ready" line with your &lt;code&gt;dotnet run&lt;/code&gt; command. Run the site on the ports it gives you and check the backoffice loads with your content and media intact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Commit it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Commit &lt;code&gt;.claude/settings.json&lt;/code&gt; and &lt;code&gt;.claude/hooks/worktree-db.ps1&lt;/code&gt; so the rest of the team gets the same setup. Leave &lt;code&gt;appsettings.Local.json&lt;/code&gt; gitignored as it always was.&lt;/p&gt;

&lt;p&gt;That's it. From now on every worktree gets its own database on the way in and loses it on the way out, and you can get back to running three features at once with a coffee in hand.&lt;/p&gt;

&lt;p&gt;Update 30/10/2026&lt;br&gt;&lt;br&gt;
Since posting this last night, I've had a number of people either comment on my socials, posted comments below or DM me.&lt;br&gt;&lt;br&gt;
I thought I'd add a couple of updates.   &lt;/p&gt;

&lt;p&gt;One possible way to get around the backoffice token issue is to setup an &lt;a href="https://umbraco.com" rel="noopener noreferrer"&gt;Umbraco&lt;/a&gt; API user in the backoffice, setup a local .env file with the credentials and then get your agent to use the Umbraco CLI Skill so it doesn't need to log in anymore. (thanks &lt;a href="https://github.com/Matthew-Wise" rel="noopener noreferrer"&gt;Matt Wise&lt;/a&gt;).  &lt;/p&gt;

&lt;p&gt;Kevin jump also read this blog and dusted off a previous idea he had and he's now released a uSync.Treebranch package - &lt;a href="https://github.com/KevinJump/uSync.TreeBranch" rel="noopener noreferrer"&gt;https://github.com/KevinJump/uSync.TreeBranch&lt;/a&gt; its still in early development but it could be worth a look at.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>claude</category>
    </item>
    <item>
      <title>Stop apologising for using AI</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:48:54 +0000</pubDate>
      <link>https://dev.to/owaincodes/stop-apologising-for-using-ai-39hf</link>
      <guid>https://dev.to/owaincodes/stop-apologising-for-using-ai-39hf</guid>
      <description>&lt;h1&gt;
  
  
  Stop apologising for using AI
&lt;/h1&gt;

&lt;p&gt;How long do you think it'll be until we stop saying "with 's help" or "I wrote this with " as if it's a bad thing? Let's admit it, if you aren't using AI in some way in your job, you're one of the very few.&lt;/p&gt;

&lt;p&gt;Take my recent code contributions to H5YR.com. Every single one of them is coauthored by me and Claude. Is that something I should be hiding? Or apologising for?&lt;/p&gt;

&lt;p&gt;I feel like we, as developers, are almost guilty that we're no longer handwriting code, so we have to caveat everything with "well, I say I wrote it, but actually AI did".&lt;/p&gt;

&lt;h2&gt;
  
  
  Did I ever really write it?
&lt;/h2&gt;

&lt;p&gt;Even in my time in this industry, things have moved along pretty quickly. The first program I wrote was in BASIC on a ZX Spectrum. It printed a hello world message to the screen. Simple, but I wrote it. Or did I? I copied it from a book. So does that count as me writing it?&lt;/p&gt;

&lt;p&gt;I then moved on from my trusty ZX Spectrum and got a PC. Now I was running MS-DOS, writing .bat files and, again, learning from magazines and books. No internet was available to me back then. Wow, I'm old.&lt;/p&gt;

&lt;p&gt;Then I got a PC that ran Windows 95 and all of a sudden a world of colour appeared before my eyes. Instead of using the command line, I had a GUI and I could code in Notepad! This was a massive step forward for me and it's when my interest in coding really started to grow.&lt;/p&gt;

&lt;p&gt;I went from Notepad to Dreamweaver, then from Dreamweaver back to Notepad because I didn't like not having control. I then started coding at school, where I was introduced to Visual Basic (and I still hacked around in the actual files in Notepad when I couldn't get the GUI to do what I wanted!).&lt;/p&gt;

&lt;p&gt;Can you see where I'm going with this? Then we got Visual Studio with IntelliSense and autocomplete, another step forward in making a developer's life easier.&lt;/p&gt;

&lt;p&gt;And let's not forget Stack Overflow. How many of us have copied an answer straight from there into our code? Plenty. But I don't remember anyone writing "I wrote this with Stack Overflow's help" in their commit messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI is just the next step
&lt;/h2&gt;

&lt;p&gt;I see AI as the next step forward, but this time it hasn't taken years for people to adopt it. It's happened, what feels like, overnight. I think this is why people feel guilty or apologetic about it.&lt;/p&gt;

&lt;p&gt;What we should be apologetic about is using it as an excuse for bad code.&lt;/p&gt;

&lt;p&gt;"Oh, I didn't write that, Claude did." Nope, sorry, you prompted it. You brought it into this world, so you own it now. It's still your code. You should understand it and you should know why it's doing what it's doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  With great power
&lt;/h2&gt;

&lt;p&gt;The models available to us now are super powerful, but we control that power. "With great power comes great responsibility." We owe it to ourselves, and our future selves, to still be proud of the code we create.&lt;/p&gt;

&lt;p&gt;Today's models learned from the code we all put out into the world, and tomorrow's will too. If the internet fills up with sloppy AI code that nobody checked, guess what the next models learn from? But if we publish code that AI helped write and we've actually reviewed and refined, the next lot of tools get better. That's on us.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what does owning it actually look like?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read your own PR before anyone else reviews it.&lt;/strong&gt; If you haven't read it, why should they?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be able to explain your own code.&lt;/strong&gt; If someone asks why it works the way it does, "Claude did it" isn't an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make sure your tests are actually useful.&lt;/strong&gt; If you use tests, check they're testing something meaningful and not just passing for the sake of it. Better yet, write the tests yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Own your code
&lt;/h2&gt;

&lt;p&gt;So, all I ask of you as a developer in 2026 is this. Own your code. Don't use Claude as an excuse. Stop even saying "Oh, I made this with Claude / Copilot / whatever you use". We all know you have. So let's just get back to building the stuff we only dreamt of in the past, and embrace the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small confession
&lt;/h2&gt;

&lt;p&gt;Yes, I see the irony. I've just spent a whole post telling you to stop saying it, so here goes. I wrote this post myself, then I asked Claude to read it, fix my grammar and help me improve it. It found more than I'd like to admit. But it's still my post, and I'm owning it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>development</category>
      <category>opinion</category>
      <category>personal</category>
    </item>
    <item>
      <title>Umbraco UK Conference</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:18:02 +0000</pubDate>
      <link>https://dev.to/owaincodes/umbraco-uk-conference-11l</link>
      <guid>https://dev.to/owaincodes/umbraco-uk-conference-11l</guid>
      <description>&lt;h2&gt;
  
  
  Umbraco UK Conference: A Few Days in London
&lt;/h2&gt;

&lt;p&gt;Coffee. Code. Repeat. And this week, a fair bit of whisky and doughnuts too.&lt;/p&gt;

&lt;p&gt;I've just got back from London after the Umbraco UK Partner Summit and the Umbraco UK Conference. It was a packed few days, and I learnt a lot, so let's get into it. Fair warning, the journey home was a bit of an adventure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wednesday: Trains and Whisky
&lt;/h3&gt;

&lt;p&gt;I jumped on the train from Edinburgh down to London on Wednesday. I arrived in time for lunch and then met up with a couple of friends, Matt Wise and Ollie Picton. We headed to the Scotch Malt Whisky Society on Greville Street in Hatton Garden.&lt;/p&gt;

&lt;p&gt;Very enjoyable afternoon. Great company, a good catch up and a chance to try the latest offerings from the SMWS. Perfect.&lt;/p&gt;

&lt;p&gt;Then an early night back at the hotel. I knew what was coming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thursday: Umbraco UK Partner Summit
&lt;/h3&gt;

&lt;p&gt;Up early, breakfast, and off to the Partner Summit.&lt;/p&gt;

&lt;p&gt;I'll be honest. I've been to Partner Summits before and they were always a bit, meh. Lots of promises, not much action. I'm very pleased to say this year was different. I went along as the Initials Labs rep and came away with pages and pages of notes. I'm really looking forward to working with the Umbraco partner team (Aaron, Jacob and Gemma) to see what's possible over the next year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Fc5wjh15k%2Fpartnersummitcoffee.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Fc5wjh15k%2Fpartnersummitcoffee.jpeg" alt="partnerSummitCoffee" width="480" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jenny Bradshaw from Moriyama and Kirstie Buchanan from Cantarus&lt;/strong&gt; gave really great talks about their own experiences with Umbraco and as partners.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aaron Green (Partner Manager, UK) and Jacob Howell (Regional Manager, UK)&lt;/strong&gt; delivered great business talks. The passion these guys have for Umbraco is refreshing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rune Strand (Product Manager, Cloud)&lt;/strong&gt; talked about the future of Umbraco Cloud. It finally feels like a product you can use in the real world.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mats Persson (CEO) and Filip Bruun Bech-Larsen (Umbraco CTO)&lt;/strong&gt; also gave great talks about Umbraco and its products, and brought all the partners up to date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in all, a brilliant day. Not something I thought I'd ever say about a business summit!&lt;/p&gt;

&lt;h3&gt;
  
  
  Thursday Evening: Flight Club
&lt;/h3&gt;

&lt;p&gt;That evening was all about socialising at Flight Club. I got to make new friends within the community and chat with fellow Umbracians I hadn't seen since Codegarden.&lt;/p&gt;

&lt;p&gt;It was great to see my old pals from Gibe too. Always a pleasure, especially when you end the night drinking whisky with Tristan. Hmm, there might be a theme here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F0ctn3psc%2Fumbraco-conference.js" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F0ctn3psc%2Fumbraco-conference.js" alt="Umbraco Conference" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Friday: Umbraco UK Conference
&lt;/h3&gt;

&lt;p&gt;Friday started with breakfast, then I met up with my colleague Jack and we walked over to CodeNode, where the conference was being hosted. We collected our swag, grabbed a coffee (the first of many) and the day kicked off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Fm2ajluwl%2Fnamebadge.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Fm2ajluwl%2Fnamebadge.jpeg" alt="NameBadge" width="480" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new Umbraco Conference brand was announced, and having the rebrand explained was really interesting. After that, Bolette Kern (Head of Products at Umbraco) gave the keynote and product update, and she did it on her birthday! Happy birthday Bolette!&lt;/p&gt;

&lt;p&gt;Here are the talks I attended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automate in Minutes, Not Sprints: Introducing Umbraco Automate, by Rick Butterfield&lt;/li&gt;
&lt;li&gt;Beyond the Happy Path: Non-Functional Requirements in the Age of AI-Assisted Engineering, by Carole Rennie Logan&lt;/li&gt;
&lt;li&gt;CSI: Umbraco, Forensic Tools for Catching Performance Killers, by Jason Elkin&lt;/li&gt;
&lt;li&gt;Ghosts of Umbraco Past: Coding Practices to Lay to Rest in 2027, by Joe Glombek&lt;/li&gt;
&lt;li&gt;The Great Umbraco Search Shake-Up: Examine, Algolia &amp;amp; Azure Compared, by Busra Sengul&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They were all great talks and I don't want to pick a favourite. I got something from every one of them.&lt;/p&gt;

&lt;p&gt;The time and effort that goes into preparing a talk is always appreciated, so #h5yr (that's high five, for the uninitiated. More details at &lt;a href="https://h5yr.com" rel="noopener noreferrer"&gt;H5YR.com&lt;/a&gt;) to everyone who spoke at the event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Flo3pco5t%2Flego.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2Flo3pco5t%2Flego.jpeg" alt="lego" width="480" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The QR code on our badges was for a fun game of The Traitors. You had to scan other people's QR codes, and if you were a Traitor, you "killed" them and they were out of the game. If you were a Faithful, your aim was to stay alive and scan as many people as you could. The more scans you got, the better the prize. The first tier was a Lego minifigure, which, thankfully, I managed to get. The top prize was a ticket to Codegarden!&lt;/p&gt;

&lt;p&gt;It was a lot of fun and a great way to get people talking to each other.&lt;/p&gt;

&lt;p&gt;Now, the food. The food at the event was great and the snacks were immense. The doughnuts were so good I could have eaten the entire box. Biscoff doughnuts. Yum!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F5vlpu1ez%2Fdonuts.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F5vlpu1ez%2Fdonuts.jpeg" alt="donuts" width="480" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The closing keynote came from Lotte Pitcher and Sebastiaan Janssen, and it was really good. Funny but informative. Oh, and did you know that backoffice menu separators are back? Yes, really.&lt;/p&gt;

&lt;p&gt;I spent the evening socialising with new and old friends and called it a night around 10pm. Back to the hotel to pack my bag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saturday: Board Games and a Very Long Way Home
&lt;/h3&gt;

&lt;p&gt;On Saturday I met up with a few friends and headed to Arcanists Tavern, a table top game cafe. A great selection of games and a really nice way to spend a few hours before heading back to Edinburgh.&lt;/p&gt;

&lt;p&gt;Then came the journey home, which was eventful. I got within 15 minutes of Edinburgh station and the train stopped. Then it started going in the opposite direction. We couldn't go any further because the signals on the track had gone out! I ended up having to ask my wife to come and pick me up, which was a 45 minute drive each way. Not great at 11pm!&lt;/p&gt;

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

&lt;p&gt;A fantastic few days in London, signal failure aside. I learnt a lot, made some new connections and caught up with old friends.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>conference</category>
      <category>london</category>
    </item>
    <item>
      <title>Beta Blast: A Bouldering Bubble Shooter Built with Claude</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Mon, 12 Oct 2026 16:30:40 +0000</pubDate>
      <link>https://dev.to/owaincodes/beta-blast-a-bouldering-bubble-shooter-built-with-claude-323b</link>
      <guid>https://dev.to/owaincodes/beta-blast-a-bouldering-bubble-shooter-built-with-claude-323b</guid>
      <description>&lt;h1&gt;
  
  
  I asked Claude for a Puzzle Bobble clone. It built a climbing game with a global leaderboard.
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Beta Blast&lt;/strong&gt;: shoot coloured climbing holds at a wall, pop three or more, send the route. Playable in your browser right now, free, no install.&lt;/p&gt;

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

&lt;p&gt;It started with one question: &lt;em&gt;"What's a good tech stack to make a replica of Puzzle Bobble?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few hours of chatting later I had a finished, deployed game. I wrote none of the code and that's OK. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🧗 &lt;strong&gt;Endless grades.&lt;/strong&gt; V0, V1, V2 and on, getting harder with no ceiling&lt;/li&gt;
&lt;li&gt;📅 &lt;strong&gt;Daily Boulder.&lt;/strong&gt; One route a day, the same for everyone&lt;/li&gt;
&lt;li&gt;⏱️ &lt;strong&gt;Survival.&lt;/strong&gt; The wall keeps dropping, the music speeds up, and the stages ramp up&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Route Setter.&lt;/strong&gt; Build your own wall and share it as a link&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;2-player over the internet.&lt;/strong&gt; Pop holds to send rows to your opponent&lt;/li&gt;
&lt;li&gt;🏆 &lt;strong&gt;A global leaderboard&lt;/strong&gt;, with a climber icon next to every score&lt;/li&gt;
&lt;li&gt;🧢 &lt;strong&gt;Customisable climber.&lt;/strong&gt; Beanie, shirt and chalk bag colours unlock as you play&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;A public API and a one-line embed&lt;/strong&gt;, so any website can show the leaderboard&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What it uses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Game&lt;/td&gt;
&lt;td&gt;TypeScript, &lt;a href="https://phaser.io" rel="noopener noreferrer"&gt;Phaser 3&lt;/a&gt;, Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Art and sound&lt;/td&gt;
&lt;td&gt;Drawn and synthesised in code. &lt;strong&gt;There are no image or audio files.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2-player&lt;/td&gt;
&lt;td&gt;PeerJS (WebRTC), so no game server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaderboard&lt;/td&gt;
&lt;td&gt;Cloudflare Workers + D1 (SQLite), on the free plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;GitHub Pages and a custom domain, deployed by GitHub Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality&lt;/td&gt;
&lt;td&gt;236 tests, a bot that plays every grade to check the difficulty, and CI on every pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why it was fun
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I made the creative calls, Claude did the typing.&lt;/strong&gt; I picked the theme (climbers shooting holds), the name, and which feature came next. Claude proposed options, built them, and opened the pull requests. I mostly said "yes, do that."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It checked its own work.&lt;/strong&gt; It ran the game in a headless browser, looked at its own screenshots and fixed what looked wrong. When I asked for a full review it found real bugs, such as a network blip that ended live matches, and fixed them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It handled the boring, essential parts.&lt;/strong&gt; Tests, CI, deploys, the Cloudflare setup, the README and the screenshots in it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anti-cheat was a good problem.&lt;/strong&gt; Scores come from the browser, so anyone can fake one. The game now records every shot and can replay a run on the server through the same engine to check the score. It's off on the free plan, because a replay costs more CPU than the free tier allows. Claude measured that, told me, and let me pick. That kind of trade-off is what I'd want from a human colleague too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It wasn't one prompt. It was a long conversation, with me playing the game and steering.&lt;/li&gt;
&lt;li&gt;Things broke along the way: a deploy that needed Pages switched on, a leaderboard that looked empty because it opened on the wrong board. We fixed each one.&lt;/li&gt;
&lt;li&gt;Claude can't tap a real phone screen or feel whether a shot is satisfying. That part is still me.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;🎮 &lt;strong&gt;Play:&lt;/strong&gt; &lt;a href="https://thesecretstage.vip/Beta-Blast/" rel="noopener noreferrer"&gt;https://thesecretstage.vip/Beta-Blast/&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/OwainWilliams/Beta-Blast" rel="noopener noreferrer"&gt;https://github.com/OwainWilliams/Beta-Blast&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the leaderboard to your own site with one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://thesecretstage.vip/Beta-Blast/embed.js"&lt;/span&gt; &lt;span class="na"&gt;data-board=&lt;/span&gt;&lt;span class="s"&gt;"daily"&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've been sitting on a "wouldn't it be cool if" project, this is your sign. Open a conversation, describe it, and see how far an afternoon goes.&lt;/p&gt;

&lt;p&gt;I've a load of other features I want to add to this, one will be an Umbraco themed version, a 4 player version and maybe some other features that I liked from old online games such as TetriNet (anyone else remember that?)&lt;/p&gt;

&lt;p&gt;If you have a feature you'd like to see, create an issue on the repo and we can chat about it.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>retrogame</category>
    </item>
    <item>
      <title>The coffee log, rebuilt in .NET</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Fri, 02 Oct 2026 11:24:10 +0000</pubDate>
      <link>https://dev.to/owaincodes/the-coffee-log-rebuilt-in-net-25md</link>
      <guid>https://dev.to/owaincodes/the-coffee-log-rebuilt-in-net-25md</guid>
      <description>&lt;h1&gt;
  
  
  The coffee log, rebuilt in .NET
&lt;/h1&gt;

&lt;p&gt;Back in June I wrote about &lt;a href="https://owain.codes/blog/2026/june/its-coffee-time" rel="noopener noreferrer"&gt;my coffee machine writing my blog&lt;/a&gt;. Smart plug watches the coffee machine, Raspberry Pi watches the plug, bit of Python tells Umbraco I've made a coffee. If you've not read that one, go and read it first, it covers the plug, the KLAP handshake, the controller on the site, all of it. None of that has changed.&lt;/p&gt;

&lt;p&gt;So why am I writing about it again? Because I've ripped out the middle bit and rewritten it in .NET. And I know, I know, "it worked, why touch it?" Fair question. Let me explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  It worked. I just couldn't remember how.
&lt;/h2&gt;

&lt;p&gt;Here's the thing. I write C# all day. I don't write Python all day. So when I came back to the coffee log after a few months, I opened the script and had to learn it all over again. Which virtual env was it? Why is that library pinned to that version? Why is that one line commented out?!&lt;/p&gt;

&lt;p&gt;The Python version was written for a Pi Zero, which meant pure Python only and being very careful about what got installed. All sensible at the time. But six months on it just felt like someone else's project.&lt;/p&gt;

&lt;p&gt;The Pi has since been upgraded to a 64 bit model, and that opened a door I couldn't resist. .NET can publish self contained. One binary, no runtime to install, no pip, no package manager, nothing. Copy it over, run it. That alone was enough for me to fire up a new solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a month of data taught me
&lt;/h2&gt;

&lt;p&gt;In June the state machine had two ideas. Power goes up and stays up for a few seconds, that's a brew starting. Power drops and stays down for a while, that's a brew finished. Simple, and it mostly worked, because my machine switches itself off after brewing so every on, brew, off is one clean session.&lt;/p&gt;

&lt;p&gt;Mostly. Every so often it would log a coffee I hadn't made, and I couldn't tell you why, because all I had was the post on the site and a vague memory of what I'd been doing that morning.&lt;/p&gt;

&lt;p&gt;This time round the service writes every brew it detects to a JSON file on the Pi as well as posting it, and honestly that file turned out to be the best thing in the whole project. A month of it and I could see exactly what was going on. Have you ever looked at a month of your own coffee habits as JSON? No? Just me then.&lt;/p&gt;

&lt;p&gt;So the detector now has a few more rules. It needs a run of readings above the threshold before it'll commit to a brew, a run below before it'll call one finished, a minimum duration so the short blips get binned, and a cooldown afterwards. That last one is the guard rail I care about most. If I make two or three cups in quick succession, say when we've got people round, it counts as one. I'm not after 100% accuracy here, it's a blog post about coffee, not a billing system. I'd much rather miss the odd cup than post about ones I never made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replay mode, or how to tune a coffee machine without drinking 40 coffees
&lt;/h2&gt;

&lt;p&gt;The service logs every raw reading to a CSV, one file per day. And it has a replay mode. Point it at a CSV instead of the plug and it runs the whole day through the detector in about a second, using the timestamps from the file rather than the clock on the wall.&lt;/p&gt;

&lt;p&gt;The trick is that the detector doesn't know or care where its readings come from. There's one interface for "give me a power reading", and either the real plug or a CSV file sits behind it. Everything downstream is identical.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://mermaid.live/edit#pako:eNpljstugzAQRX_Fmk03gCCAHLzoJtm06ktNVanBWVh4QmjBRrZpkiL-vYAaVWp3586cefRQaInAYF_rY3EQxpG7Z264auuuzF9Eq8lEO-L716RBh2ZqFvYzf0M7JinOV5asNq9_jBn6_uZJH9HcT2EYZkOOXDht8vUP7Cb9Up2Vd6tVfrt5fCD7qsb_fVs5zPVRVCqYnrc78KA0lQTmTIceNGgaMUXouSKEgztggxzYiAZld_KlMB9-oWttOHA1jPOtUFutm8sKo7vyAGwvajumrpXC4boSpRG_Co5fmZXulAOWLuYVwHo4AaM0iGiYJMswS2gahUsPzsCiNAyiRZLROF7QOMyyePDgaz4aBkuaDt9W14RT" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;mermaid.live&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Swap the box on the left and nothing else changes. That's the whole reason it's tunable.&lt;/p&gt;

&lt;p&gt;Nothing gets written in replay mode. No CSV, no state, no posts to the site, it doesn't even need credentials. So I can copy a month of captures off the Pi, replay them on the desktop with different settings, and check the results against the times I know I made a coffee. Change a number, run it, a second later I know if it helped. No standing at the machine pulling shots I don't want. No risk of publishing a post about a coffee from August either, which, trust me, I thought about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I stopped retrying
&lt;/h2&gt;

&lt;p&gt;Where a brew goes is now pluggable. There's a JSON file sink, an Umbraco sink, and a mode that does both, and I run both.&lt;/p&gt;

&lt;p&gt;In June the Pi had a little pending queue that kept retrying a post until the site answered. Sensible, right? Until you remember the controller creates a node every single time it's called, and there's nothing in the request that lets the site spot a repeat. So if the site processed the request but the response got lost on the way back, the retry publishes the same coffee twice. Two blog posts, one coffee. Nobody needs that.&lt;/p&gt;

&lt;p&gt;So the .NET sink deliberately never retries. If a post fails it gets logged and the brew's still sitting in the JSON file on the Pi. A missing post I can fix by hand in a minute. A duplicate one I have to notice first, and I won't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The afternoon I lost to two keys
&lt;/h2&gt;

&lt;p&gt;Right, confession time. Wiring the Pi back up to the site after a few months away, I went into the backoffice and created an API user. Because that's how you talk to Umbraco from outside, right? Management API, client id, secret, OAuth, all of that.&lt;/p&gt;

&lt;p&gt;Not here it isn't. The controller from the June post isn't the Management API at all, it's a plain controller sat inside the site, and the only thing guarding it is one shared secret in an &lt;code&gt;X-Api-Key&lt;/code&gt; header. No OAuth, no client id, nothing to create in the backoffice. The same string just has to live in two places, an environment variable on the Live environment in Umbraco Cloud, and the env file on the Pi. That's it. I'd built the thing and forgotten how it worked. Again.&lt;/p&gt;

&lt;p&gt;And then, because one mistake is never enough, I put the secret in the wrong setting. The site also needs to know which node to create posts under, and that's a GUID in a setting called BlogParentKey, which sits right next to the API key in the Cloud portal. So for a good while I had a password sat in a field that wanted a GUID and was staring at 401s wondering what on earth was going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying is one command now
&lt;/h2&gt;

&lt;p&gt;The service runs under systemd as its own service account, credentials in a separate env file, and the unit file locks it right down. It can only write its own data folder, it can't see home directories, and it never gives up restarting after a power cut, which on a Pi is a matter of when, not if.&lt;/p&gt;

&lt;p&gt;Deploying is a single script. Build for arm64, copy the binary over SSH, stop the service, install, start, print the status. It only ever replaces the binary and the default settings. It never touches the env file, the captured data or the detector's saved state, so I can run it at three in the afternoon and nothing gets lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's actually different?
&lt;/h2&gt;

&lt;p&gt;Same plug. Same site. Same controller. Same daft idea. What's changed is that I can open this project after six months away, run the tests, replay a day of data to see what the detector thinks, and deploy with one command. It went from a thing I built to a thing I can actually maintain. Which, if you read my last post, is sort of the point I've been circling round for a while now.&lt;/p&gt;

&lt;p&gt;Right then. Coffee. You'll know when I have.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>dotnet</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>AI as a learning tool</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Fri, 02 Oct 2026 09:34:35 +0000</pubDate>
      <link>https://dev.to/owaincodes/ai-as-a-learning-tool-1iec</link>
      <guid>https://dev.to/owaincodes/ai-as-a-learning-tool-1iec</guid>
      <description>&lt;h1&gt;
  
  
  Using AI to Become a Better Developer (A Promise to Myself)
&lt;/h1&gt;

&lt;p&gt;Alright, let's be honest. I ask Claude for code. I read maybe three lines of it. I think "yeah that looks right" and paste it into my editor or just accept it in to my repo. Quick test. Ship it. Move on.&lt;/p&gt;

&lt;p&gt;That's my current workflow. Sloppy but honest.&lt;/p&gt;

&lt;p&gt;And it works. Most of the time. Which is the exact problem.&lt;/p&gt;

&lt;p&gt;When something works, there's no incentive to understand it. No incentive to question it. No incentive to actually learn anything from it. I'm not becoming a better developer. I'm becoming faster at not thinking.&lt;/p&gt;

&lt;p&gt;This ends next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;I've got enough experience that I can get away with this for a while. But I can feel it. The edges are getting blurry. There's code in repos I've written that I couldn't explain to someone else. Not because it's complex. Because I never actually understood it in the first place.&lt;/p&gt;

&lt;p&gt;That's not sustainable. That's not how you stay sharp.&lt;/p&gt;

&lt;p&gt;The difference between using AI as a tool and using AI as a crutch is simple. It's the difference between asking "why" and just accepting.&lt;/p&gt;

&lt;p&gt;When I stop asking why, I stop learning. And when I stop learning, I stop being useful.&lt;/p&gt;

&lt;p&gt;So next week, that changes. I'm going to Claude with a different mindset. Not "give me the answer." But "help me understand this."&lt;/p&gt;

&lt;h2&gt;
  
  
  How I'm Going to Use It Differently
&lt;/h2&gt;

&lt;p&gt;Instead of copy, paste, ship, I'm going to actually engage with it. When Claude gives me something, I'm going to ask myself better questions.&lt;/p&gt;

&lt;p&gt;Why did it choose this approach? What's the alternative? What could go wrong here? Is there a trade-off I'm missing?&lt;/p&gt;

&lt;p&gt;Sometimes Claude's right and I'll learn why. Sometimes it's half-right and I'll learn how to adapt it. Sometimes it's confidently wrong and I'll learn more from fixing it than if it had been perfect.&lt;/p&gt;

&lt;p&gt;The key thing is that I'm actually thinking now. Claude's the rubber duck that talks back.&lt;/p&gt;

&lt;p&gt;What changes:&lt;/p&gt;

&lt;p&gt;I stop asking for solutions. I start asking for explanations.&lt;/p&gt;

&lt;p&gt;I stop reading three lines. I read it all and actually process it.&lt;/p&gt;

&lt;p&gt;I stop shipping immediately. I challenge the output. Play devil's advocate with the code.&lt;/p&gt;

&lt;p&gt;I use it to learn new patterns, not to skip learning patterns I should already know.&lt;/p&gt;

&lt;p&gt;I ask "why this way" instead of just accepting "here's a way."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge to You
&lt;/h2&gt;

&lt;p&gt;If you're doing what I'm doing right now, I get it. It's faster. But you're missing out on actually learning anything.&lt;/p&gt;

&lt;p&gt;Start next week. Change one habit. When you get an answer from AI, before you do anything else, ask it why it chose that approach. Make it explain its reasoning. Make it justify it.&lt;/p&gt;

&lt;p&gt;You'll be surprised how often that extra five minutes of questioning reveals something you didn't know.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Prompts That Actually Work
&lt;/h2&gt;

&lt;p&gt;These are the ones I'm going to use. Steal them if they help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop saying:&lt;/strong&gt; "Write me a function that does X"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start saying:&lt;/strong&gt; "Show me how to [specific task]. Walk me through why you'd approach it this way instead of [alternative approach]."&lt;/p&gt;

&lt;p&gt;Forces them to explain the thinking, not just dump code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop saying:&lt;/strong&gt; "Fix this code"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start saying:&lt;/strong&gt; "This code does X but I think there's a better way. What are the trade-offs between my current approach and alternatives? Which would you recommend and why?"&lt;/p&gt;

&lt;p&gt;Now you get the reasoning behind the fix, not just a quick patch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop saying:&lt;/strong&gt; "How do I do X"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start saying:&lt;/strong&gt; "I need to [outcome]. What are the three most common approaches? What's the trade-off between them? What would you pick for [your specific context] and why?"&lt;/p&gt;

&lt;p&gt;Forces options and reasoning, not just one answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop saying:&lt;/strong&gt; "Write me some code"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start saying:&lt;/strong&gt; "I'm trying to solve [problem]. Before you write code, help me understand the concepts and patterns I should know about. Then show me how they apply here."&lt;/p&gt;

&lt;p&gt;Learn the foundation first. The code comes after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And always follow up with:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"I don't understand why you chose this. Explain it like I've never seen this pattern before. What could break? What are the limitations?"&lt;/p&gt;

&lt;p&gt;Push back. Ask the hard questions. Don't accept the first answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actually Doing This
&lt;/h2&gt;

&lt;p&gt;The developers getting real value from AI aren't the ones who use it to skip thinking. They're using it to think better.&lt;/p&gt;

&lt;p&gt;So next week. That's when I start. I'm going to use Claude to learn, not just to ship. I'm going to ask why instead of just accepting the answer.&lt;/p&gt;

&lt;p&gt;And maybe you should too.&lt;/p&gt;

&lt;p&gt;We'll see how this goes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
    </item>
    <item>
      <title>Umbraco "hidden" properties - umbracoUrlName and umbracoUrlAlias</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Wed, 06 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/umbraco-hidden-properties-umbracourlname-and-umbracourlalias-4mjl</link>
      <guid>https://dev.to/owaincodes/umbraco-hidden-properties-umbracourlname-and-umbracourlalias-4mjl</guid>
      <description>&lt;p&gt;While working on a project I came across an issue where having the ability to give a different url for a page within the CMS would be beneficial. &lt;/p&gt;

&lt;p&gt;In the past, I've always just used &lt;code&gt;umbracoUrlAlias&lt;/code&gt; but it was suggested that &lt;code&gt;umbracoUrlName&lt;/code&gt; might be a better option. This short blog will hopefully bring these properties to light for some of you. &lt;/p&gt;

&lt;h2&gt;
  
  
  How do you use them?
&lt;/h2&gt;

&lt;p&gt;Both properties can be use on any document type that is accessible via the front end of your site e.g. &lt;a href="http://www.owain.codes/about" rel="noopener noreferrer"&gt;http://www.owain.codes/about&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a text string to your document type and make sure that the alias is &lt;code&gt;umbracoUrlAlias&lt;/code&gt; or &lt;code&gt;umbracoUrlName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An example is below for &lt;code&gt;umbracoUrlName&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F8d8b27e1a7c69f0%2Fpropertyeditor.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fowain.codes%2Fmedia%2F8d8b27e1a7c69f0%2Fpropertyeditor.jpg" alt="enter image description here" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, you can change the Name from &lt;code&gt;umbracoUrlName&lt;/code&gt; but make sure the alias, the bit  next to the padlock, remains &lt;code&gt;umbracoUrlName&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the difference between the two?
&lt;/h2&gt;

&lt;p&gt;I'll use the description that is used within the &lt;a href="https://our.umbraco.com/Documentation/Reference/Routing/routing-properties" rel="noopener noreferrer"&gt;Umbraco Documentation&lt;/a&gt;, &lt;/p&gt;

&lt;h3&gt;
  
  
  umbracoUrlAlias
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This property when created as a text string lets you provide a comma separated list of alternate full URL paths for the node. For example, if your URL was /some-category/some-page/content-node, by adding an umbracoUrlAlias of "flowers", a user can navigate to the node by going to /flowers. The URL alias remains in the browser address bar as a 'mask' over the real URL. You can also specify paths like "flowers/roses/red".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One thing to note is that the original URL is also still accessible. To help understand this, if you think about this property as a url shorterner then it will hopefully make it a bit clearer. &lt;/p&gt;

&lt;h3&gt;
  
  
  umbracoUrlName
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This property when created as a text string lets you provide a different URL name to what is created by default by the name of the node. If you enter a value for this property and save/publish the content node, you will see that its main URL is updated with a new path suffix.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we use the example that was used above and use the path &lt;code&gt;/some-category/some-page/content-node&lt;/code&gt;, if we use &lt;code&gt;umbracoUrlName&lt;/code&gt; property and use the word &lt;code&gt;flowers&lt;/code&gt;as the value, the url for this page / node is now &lt;code&gt;/flowers&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The longer path name now doesn't exist and the only way to access this node is via &lt;code&gt;/flowers&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Other routing options
&lt;/h3&gt;

&lt;p&gt;There are two other options built in to Umbraco that you can use when handling routing. &lt;br&gt;
&lt;code&gt;umbracoRedirect&lt;/code&gt; and &lt;code&gt;umbracoRedirectId&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In short, &lt;code&gt;umbracoRedirect&lt;/code&gt; performs a 302 redirect by using a Content Picker and &lt;code&gt;umbracoRedirectId&lt;/code&gt; does a url rewrite. &lt;/p&gt;

&lt;p&gt;For more info, check out the &lt;a href="https://our.umbraco.com/Documentation/Reference/Routing/routing-properties" rel="noopener noreferrer"&gt;Umbraco Documentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>umbraco</category>
    </item>
    <item>
      <title>Null Conditional Operator for fallback images</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Thu, 10 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/null-conditional-operator-for-fallback-images-4akg</link>
      <guid>https://dev.to/owaincodes/null-conditional-operator-for-fallback-images-4akg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4t1vobhcjaccdaulmf89.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4t1vobhcjaccdaulmf89.jpg" alt="Generic Header image" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last night when working on my new version of &lt;a href="https://www.owain.codes" rel="noopener noreferrer"&gt;Owain.Codes&lt;/a&gt; I managed to blow up the site. &lt;/p&gt;

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

&lt;p&gt;I hadn't put an image in to the CMS and when I rendered out the image or the lack of an image on the front end, I got a null exception error.&lt;/p&gt;

&lt;p&gt;I started to write a bit of code to handle this which looked a bit like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if(Model.Image.Url() == null)
{
  set default image as fallback.jpg
}
else{
  use the image from the CMS
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then I thought, there must be a nicer way to handle fallbacks, and I believe I have found that nicer option and its by using the &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-" rel="noopener noreferrer"&gt;Null Conditional Operator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I am now using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; var blogImage = Model.PageBanner?.Url() ?? "assets/images/noImage.jpg";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if the &lt;code&gt;Model.PageBanner&lt;/code&gt; is null, give me the noImage.jpg fall back string, else give me the &lt;code&gt;Model.PageBanner.Url()&lt;/code&gt;. I then use the variable blogImage on my page.&lt;/p&gt;

&lt;p&gt;I now don't need to worry about whether I set an image for a blog or not. Either way I will have an image on my page.&lt;/p&gt;

</description>
      <category>csharp</category>
    </item>
    <item>
      <title>Restart Visual Studio as Admin in seconds</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Fri, 04 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/restart-visual-studio-as-admin-in-seconds-2jj9</link>
      <guid>https://dev.to/owaincodes/restart-visual-studio-as-admin-in-seconds-2jj9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fblpq5opj2d49443oovdq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fblpq5opj2d49443oovdq.jpg" alt="blogcomputerimagelrg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week &lt;a href="https://twitter.com/HotChilliCode" rel="noopener noreferrer"&gt;@HotChilliCode&lt;/a&gt; aka Nik shared this &lt;a href="https://twitter.com/shanselman/" rel="noopener noreferrer"&gt;Scott Hanselman&lt;/a&gt; video with me : "Overwhelmed with Programming? Here's small things to help - Computer Stuff They Didn't Teach You #14"&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/5CmjW_8ief4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;During the video Scott talks about finding ways to reduce repetition. If you find yourself doing something over and over again, is there a way to speed it up? Can you automate it in some way.&lt;/p&gt;

&lt;p&gt;Well today I found myself, for probably the 100th day in a row, needing to attach my debugger in Visual Studio to my local IIS instance. The only issue was, I wasn't running in Admin mode.&lt;/p&gt;

&lt;p&gt;I needed to close visual studio, right click the icon, open as Admin then reload my solution. Not a massive issue but I had noticed I've been doing this a lot recently as Visual Studio for me doesn't automatically run in Admin mode. I know, I could set it up to run in Admin mode straight away but then if I am doing screen shares, the person I'm sharing with can't interact with my VS screen so then I need to shutdown VS, remove the setting to start as Admin. It's just a pain.&lt;/p&gt;

&lt;p&gt;Anyway, I found an Visual Studio Extension called &lt;a href="https://github.com/ilmax/vs-restart" rel="noopener noreferrer"&gt;Visual Studio Restart&lt;/a&gt; and all it does is, it adds a Link in your File menu that will shutdown VS, restart it in Admin mode and also reload the solution you are working on. Brilliant! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: I am using VS2019 Community Edition, I've not tested this in any other versions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next thing I did was I &lt;a href="https://docs.microsoft.com/en-us/visualstudio/ide/identifying-and-customizing-keyboard-shortcuts-in-visual-studio?view=vs-2019" rel="noopener noreferrer"&gt;assigned a shortcut key&lt;/a&gt; to the restart command. The key combo I used is &lt;code&gt;Ctrl+ '&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So now, if I am working on a project that I haven't loaded as Admin, I tap &lt;code&gt;Ctrl + '&lt;/code&gt; and boom, I'm up and running in Admin mode in seconds.&lt;/p&gt;

</description>
      <category>visualstudio</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Load Windows Terminal from Explorer Window</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Wed, 02 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/load-windows-terminal-from-explorer-window-30f4</link>
      <guid>https://dev.to/owaincodes/load-windows-terminal-from-explorer-window-30f4</guid>
      <description>&lt;p&gt;Today I found myself once again trying to be more efficient with my keystrokes and mouse movements and it got me wondering. If I am in a folder, can I open up a Windows Terminal from that folder?&lt;/p&gt;

&lt;p&gt;First of all, let me tell you what Windows Terminal is - basically, its Windows Command Prompt on steroids.&lt;/p&gt;

&lt;p&gt;Official description is: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Windows Terminal is a modern, fast, efficient, powerful, and productive terminal application for users of command-line tools and shells like Command Prompt, PowerShell, and WSL. Its main features include multiple tabs, panes, Unicode and UTF-8 character support, a GPU accelerated text rendering engine, and custom themes, styles, and configurations."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's also open source so you can play around with it and really customise things if you wants.&lt;/p&gt;

&lt;p&gt;You can download Windows Terminal from &lt;a href="https://www.microsoft.com/en-gb/p/windows-terminal/9n0dx20hk701?activetab=pivot:overviewtab" rel="noopener noreferrer"&gt;Microsoft.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solve the problem
&lt;/h2&gt;

&lt;p&gt;I know that if I type &lt;code&gt;cmd&lt;/code&gt; in to the explorer path area it will load up the command prompt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe0o61i02khirxlpeehpg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe0o61i02khirxlpeehpg.gif" alt="cmdprompt" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I didn't realise was if I type in 'wt' it will load up a new instance of Windows Terminal. However, this just opens up a new Terminal but you still need to navigate to the folder you were in via the command line. That's not really any saving in time.&lt;/p&gt;

&lt;p&gt;After a bit of googling, what I found was, you can edit the settings for Windows Terminal.&lt;/p&gt;

&lt;p&gt;If you open up Setting via the dropdown menu in your Windows Terminal....&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm7a3wdsnwl03op5w7c9m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm7a3wdsnwl03op5w7c9m.gif" alt="wtsetup" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you need to add the &lt;code&gt;"startingDirectory": "."&lt;/code&gt; to each of the profiles within the settings.json file.&lt;/p&gt;

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

&lt;p&gt;Now when you save this file, and go back to Explorer and type in wt, your terminal window will load and direct you to the folder path that you were originally in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus content
&lt;/h3&gt;

&lt;p&gt;I also decided to add a bit of transparency to the Windows Terminal by adding the two additional values - &lt;code&gt;"useAcrylic": true&lt;/code&gt; and &lt;code&gt;"acrylicOpacity": 0.5&lt;/code&gt;you can obviously change the 0.5 to any value you like to make the transparency work better for you. One thing to look out for though, if you don't have transparceny effect turned on in your Windows settings, it doesn't matter what you do in the settings.json file, your terminal won't show any difference.&lt;/p&gt;

&lt;p&gt;To turn transparency on, go to Start Menu -&amp;gt; Settings -&amp;gt; Personalisation -&amp;gt; Colours and then toggle the Transparency Effects to On.&lt;/p&gt;

&lt;p&gt;Now load up a terminal and it should have a transparent background, you can even put an image there instead and have a different image for different terminal windows. Check out the &lt;a href="https://docs.microsoft.com/en-us/windows/terminal/customize-settings/profile-settings" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for all your options - go wild!&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>howto</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Obsolete Umbraco Property Types.</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Thu, 09 Jul 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/obsolete-umbraco-property-types-2bh9</link>
      <guid>https://dev.to/owaincodes/obsolete-umbraco-property-types-2bh9</guid>
      <description>&lt;p&gt;Yesterday I had a bit of a panic. I was bug fixing something for a client and I changed the property editor from (Obsolete) Media Picker to Media Pick... &lt;a href="https://www.owain.codes/blog/posts/2020/july/obsolete-umbraco-property-types/" rel="noopener noreferrer"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>configuration</category>
    </item>
    <item>
      <title>Umbraco Uno - A first glance</title>
      <dc:creator>Owain Williams</dc:creator>
      <pubDate>Thu, 18 Jun 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/owaincodes/umbraco-uno-a-first-glance-3f6b</link>
      <guid>https://dev.to/owaincodes/umbraco-uno-a-first-glance-3f6b</guid>
      <description>&lt;p&gt;Umbraco Uno - A first glance Umbraco Uno - The one-stop website platform for creative agencies. That's the tag line Umbraco have given their latest... &lt;a href="https://www.owain.codes/blog/posts/2020/june/umbraco-uno/" rel="noopener noreferrer"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
