<rss version="2.0">
  <channel>
    <title>Screenshots on Leon Mika</title>
    <link>https://lmika.org/categories/screenshots/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Sun, 08 Mar 2026 10:11:03 +1100</lastBuildDate>
    
    <item>
      <title>Devlog: CSVTool - A Vibe-coded CSV Editor</title>
      <link>https://lmika.org/2026/03/08/devlog-csvtool-a-vibecoded-csv.html</link>
      <pubDate>Sun, 08 Mar 2026 10:11:03 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/03/08/devlog-csvtool-a-vibecoded-csv.html</guid>
      <description>&lt;p&gt;One of the fun aspects of these new code agents is seeing what they&#39;re capable of producing just form the prompt, so called &#34;vibe-coding.&#34; There are some that are definitely all in on the concept: I&#39;m thinking of Steve Yeggie and his Gas Town work. As for myself, I still prefer to be a bit more hands on. But it&#39;s still amusing to see what these agents are capable of just from the prompt.&lt;/p&gt;
&lt;p&gt;I got inspired by &lt;a href=&#34;http://scripting.com/2026/03/03.html#a145455&#34;&gt;Dave Winder&#39;s post&lt;/a&gt; about how he asked Claude Code to make a spreadsheet app for him. I use a TUI app I made for myself called &lt;a href=&#34;https://lmika.dev/cmd/ted&#34;&gt;Ted&lt;/a&gt; that I use to edit CSV files, but seeing how good Claude was in making the spreadsheet, I thought I&#39;d ask Claude to make me a GUI version. This is what it came up with:&lt;/p&gt;
&lt;img src=&#34;https://devlog.lmika.org/uploads/2026/03/egPA1yBj7FtPZMez.png&#34; alt=&#34;Screenshot of CSVTool, showing a window with a spreadsheet like table&#34;&gt;
&lt;p&gt;The results were pretty decent, at least on the surface. I haven&#39;t put it quite through it&#39;s paces for editing large CSV files, but what it managed to do out of the box was pretty impressive. Not that it&#39;s groundbreaking in any technical sense: think spreadsheet without the ability to define expressions. Most of the supportable commands are available via a command palette, invokable using Cmd+P. There are a few copy-and-paste options for getting table data out into usable formats, something I find quite frustrating with the spreadsheets I use. You can paste table data as CSV, a Markdown table, and Jira markup. There&#39;s the usual load, save, and commands to insert rows; all pretty standard.&lt;/p&gt;
&lt;img src=&#34;https://devlog.lmika.org/uploads/2026/03/t6EU6LO8pFu-7AvL.png&#34; alt=&#34;Screenshot of the command palette of CSVTool, showing a bunch of copy options&#34;&gt;
&lt;p&gt;It isn&#39;t perfect though. Despite two attempts to instruct it to make the header row fixed such that it won&#39;t scroll off the top, Claude was unable to achieve this. The techniques of making such a header are pretty hacky: the one I&#39;m aware of is making a second table with just the header, placing it above the first, setting the position to absolute, then adding some JavaScript to keep the column widths in sync. Granted, this worked a good 18 years ago, and for tables that didn&#39;t scroll horizontally. Maybe there&#39;s a better way of doing this nowadays? Oh and speaking of the columns, despite providing affordances for resizing the columns, Claude didn&#39;t implement the code to actually resize them. This makes me wonder if HTML tables are probably not the best approach for such a control. I guess I probably should&#39;ve told Claude that.&lt;/p&gt;
&lt;p&gt;In any case, I&#39;m not sure I&#39;ll put this to any real use. One thing I like about Ted is that it&#39;s modal, like Vim. Being able to move around using the keyboard (I, J, K, and L are mapped to up, left, down, and right, respectively) is something I&#39;d miss. So I think Ted will still be useful for me. I may revisit this in the future though, and see if Claude could make a GUI version of Ted. That could be interesting.&lt;/p&gt;
&lt;p&gt;I&#39;ll finish up by talking about some of the other features I asked to be added. Some of them were pretty ill conceived, as they came while I had a flurry of needs for CSV data. I ended up using to tool more frequently than I expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sorting:&lt;/strong&gt; Sort the table (the actual model, not just the view) either alphanumeric ascending, or alphanumeric descending based on the values of the current cell. There was also a &#34;Sort Advanced&#34; that prompted the user to enter the columns to sort in priority order (although not the direction, it&#39;s always alphanumeric ascending).&lt;/p&gt;
&lt;img src=&#34;https://devlog.lmika.org/uploads/2026/03/RDDP-wu2pcJ1j8Ij.png&#34; alt=&#34;Screenshot of the Sort Advanced modal of CSVTools, showing a form with first name and last name selected&#34;&gt;
&lt;p&gt;&lt;strong&gt;Match Cell:&lt;/strong&gt; Select cells that had a value matching the current cell. This was an attempt of selecting rows to delete, but it never really worked well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set Where:&lt;/strong&gt; Prompts the user for a source column, a set of match values, and a target value. Whenever the source column has one of the match values, it will set the current column to the target value. This acts like a poor man&#39;s inner join, and was made as I needed to interleave values I had from a shell command in the table I was working on.&lt;/p&gt;
&lt;img src=&#34;https://devlog.lmika.org/uploads/2026/03/l_IEN7YI3s9Rg0jv.png&#34; alt=&#34;Screenshot of the Set Where modal of CSVTools&#34;&gt;
</description>
    </item>
    
    <item>
      <title>PostgreSQL Docker and &#34;No space left on device&#34;</title>
      <link>https://lmika.org/2026/03/06/was-trying-to-launch-a.html</link>
      <pubDate>Fri, 06 Mar 2026 14:57:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/03/06/was-trying-to-launch-a.html</guid>
      <description>&lt;p&gt;I was trying to launch a PostgreSQL Docker container and was seeing it fail with the following error:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;initdb: error: could not create directory &amp;#34;/var/lib/postgresql/data/pg_wal&amp;#34;: No space left on device
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I checked my disk usage and I had plenty of storage left, almost half. Did an online search and discovered that Docker caps the amount of storage set aside for volumes. And that was set criminally low, about 119.21 GB or so, which is 6% of what&amp;rsquo;s available:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260306-145512.png&#34; width=&#34;600&#34; height=&#34;546&#34; alt=&#34;Auto-generated description: It shows a graphical interface with adjustable sliders for setting limits on CPU, memory, swap, and disk usage.&#34;&gt;
&lt;p&gt;Raising the storage to 1 TB seemed to resolve the problem.&lt;/p&gt;
&lt;p&gt;Kind of figured we&amp;rsquo;ve learnt from the issues with capping resources like this. Anyone who remembers the bad old days of adjusting the heap size of Java would surely get PTSD from dealing with this sort of rubbish. I know I did. I understand that for a shared instances or production setups, fair enough: cap the storage so it won&amp;rsquo;t go out of bounds. But for a standard laptop setup, just use all resources that&amp;rsquo;s available to you.&lt;/p&gt;
&lt;p&gt;Anyway, if anyone else using PostgreSQL in Docker encounters this, try the method above. You can find it by opening Docker Desktop, then going to Settings → Resources.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/03/06/ooh-this-is-a-nice.html</link>
      <pubDate>Fri, 06 Mar 2026 10:22:56 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/03/06/ooh-this-is-a-nice.html</guid>
      <description>&lt;p&gt;Ooh, this is a nice new feature of Pocket Casts: the web player now includes chapter markers in the scrubber. You can mouse over them to get the chapter name, and click them to jump to the chapter.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260306-102013.png&#34; width=&#34;600&#34; height=&#34;157&#34; alt=&#34;Auto-generated description: A music player interface shows the song Long Weekend by Hemispheric Views being played at 0:34 out of 5:11.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/03/01/how-is-it-that-people.html</link>
      <pubDate>Sun, 01 Mar 2026 16:58:06 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/03/01/how-is-it-that-people.html</guid>
      <description>&lt;p&gt;How is it that people making screenshot mark-up apps still don&amp;rsquo;t understand that the blend mode for highlights should be multiply, not mix with alpha. A real highlighter would keep the text black, and won&amp;rsquo;t produce obvious overcoating. This just looks like I&amp;rsquo;m smearing yellow paint everywhere.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/42532a3bd5.png&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Micro apps - Some Score Cards</title>
      <link>https://lmika.org/2026/02/01/devlog-microapps-some-score-cards.html</link>
      <pubDate>Sun, 01 Feb 2026 10:05:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/02/01/devlog-microapps-some-score-cards.html</guid>
      <description>&lt;p&gt;In the spirit of maintaining a document of what I&amp;rsquo;ve been working on, and being somewhat inspired by &lt;a href=&#34;https://birchtree.me/tag/micro-apps/&#34;&gt;Matt Birchler&amp;rsquo;s posts about his micro apps&lt;/a&gt;, I&amp;rsquo;d thought I&amp;rsquo;d document on some of the small apps I&amp;rsquo;ve worked on recently.&lt;/p&gt;
&lt;p&gt;The fact of the matter is that I&amp;rsquo;ve been building quite a few of these apps over the course of the summer, primarily in response to a specific need I have at the time. I haven&amp;rsquo;t written about them before, mainly because there didn&amp;rsquo;t feel like there was much to say. Some were vibe coded, and saying that I &amp;ldquo;made&amp;rdquo; them didn&amp;rsquo;t feel correct. Others were made by hand, but they were super simple and there was no real challenge in making them at all. In either case, being able to say &amp;ldquo;I made it&amp;rdquo; is difficult as the amount of effort spent in making it is quite low, and that doesn&amp;rsquo;t make for interesting blog posts. Would a post saying that &amp;ldquo;I went to work today&amp;rdquo; be worth reading if that&amp;rsquo;s what I do every weekday?&lt;/p&gt;
&lt;p&gt;But regardless of what my feelings are, the scales were tipped ever so slightly into wanting to write about them. So that&amp;rsquo;s what I&amp;rsquo;ll do. And there is some leeway in writing about the small things if I were to put them in the Devlog category, which could be seen as my public workbook.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s set expectations by talking about a series of score-keeping apps. These are among the simplest micro-apps I have, yet they do have their uses. You&amp;rsquo;re always left high and dry with a need to keep score of some ad-hoc game made in the moment (or, at least, I am). Notebooks and spreadsheets could work here, but I don&amp;rsquo;t carry a paper notebook, and use a digital notebook for this is always a little cumbersome. And a spreadsheet just feel a little overkill. Hence, the want for having a simple web-app for keeping scores:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260201-100150.png&#34; width=&#34;600&#34; height=&#34;409&#34; alt=&#34;Auto-generated description: A scorecard interface displays scores for four players over nine rounds, with options to undo or reset.&#34;&gt;
&lt;p&gt;Using it couldn&amp;rsquo;t be simpler: enter the score under the specific player, and it will be recorded in a table, alongside a running total. If you make a mistake, tap &amp;ldquo;Undo&amp;rdquo;. If you want to clear the scorecard, tap &amp;ldquo;Reset&amp;rdquo;. The scores are stored in local-storage so there&amp;rsquo;s nothing server side that&amp;rsquo;s running (although it does mean they&amp;rsquo;re tied to your browser). Focus automatically moves to the player to the right, but any entry could be entered at any time.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;a href=&#34;https://tools.lmika.app/scorecard-2p/&#34;&gt;two player&lt;/a&gt; and &lt;a href=&#34;https://tools.lmika.app/scorecard-4p/&#34;&gt;four player&lt;/a&gt; variant. I was considering having a &amp;ldquo;player entry&amp;rdquo; screen to select the number of players, but that felt like adding too much complexity for something that&amp;rsquo;s designed to be quick to implement.&lt;/p&gt;
&lt;p&gt;In fact, much of this was already implemented in the form of a &lt;a href=&#34;https://tools.lmika.app/finska/&#34;&gt;Finska scorecard&lt;/a&gt;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260201-100202.png&#34; width=&#34;600&#34; height=&#34;409&#34; alt=&#34;Auto-generated description: A Finska scorecard displaying scores for Team A and Team B, with Team A at 12 points and Team B at 25 points.&#34;&gt;
&lt;p&gt;This is not a new project, I just moved it from a dedicated domain name and changed the styling. It&amp;rsquo;s been a while since I needed it for a game of Finska: I was more often than not using it for generic scorekeeping. But there were some aspects of scoring Finska which made it useful for that, and less useful for anything generic. For those who never played Finska, the goal is to get to exactly 50 points. If you go over 50, you go back to 25 points. There&amp;rsquo;s also a mode where all the zero point entries are highlighted to indicate a foul (3 fouls means you loose the game).&lt;/p&gt;
&lt;p&gt;And, that&amp;rsquo;s it! Really, there&amp;rsquo;s nothing more to say about these apps. I had a need and some free time, so I put these together. There is one other scoring app that I&amp;rsquo;ve made, but that&amp;rsquo;s a little more complicated and warrants it&amp;rsquo;s own post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/01/31/more-work-on-assets-for.html</link>
      <pubDate>Sat, 31 Jan 2026 10:38:13 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/01/31/more-work-on-assets-for.html</guid>
      <description>&lt;p&gt;More work on assets for my niece&amp;rsquo;s game. This time I tried something organic: a tree. Not the most difficult organic thing, I agree. Need to build up to the hard things.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260131-103550.png&#34; width=&#34;600&#34; height=&#34;972&#34; alt=&#34;Auto-generated description: A digital illustration displays the progressive creation of a tree design using vector graphics tools, starting with basic shapes and evolving into a detailed tree with fruits or blossoms.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/01/26/was-not-expecting-to-spend.html</link>
      <pubDate>Mon, 26 Jan 2026 09:36:01 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/01/26/was-not-expecting-to-spend.html</guid>
      <description>&lt;p&gt;Was not expecting to spend yesterday morning working on Dequoter, my Boop clone. Opened it up to do some light work and when I looked up, a couple of hours have passed. Added a few more processors to deal with lines, such as splitting and joining on commas. Added a status bar for processes that return information rather than filter text, such as returning a line count.&lt;/p&gt;
&lt;p&gt;Also integrated UCL, because of course I did. Added two commands: a &lt;code&gt;UCL: Evaluate&lt;/code&gt; which executes the input as a UCL script and displays the response in the status bar, and a &lt;code&gt;UCL: Replace&lt;/code&gt; which replaces the UCL script with it&amp;rsquo;s output. This makes it possible to generate text from scripts, like a bunch of lines to test out the line count processor:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;map (seq 20) { |n| &amp;#34;Line $n&amp;#34; } | strs:join &amp;#34;\n&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260126-093456.png&#34; width=&#34;600&#34; height=&#34;478&#34; alt=&#34;Auto-generated description: A computer window titled Dequoter displays a list numbered from 0 to 19.&#34;&gt;
&lt;p&gt;It&amp;rsquo;ll also make for a useful scratchpad for testing out some UCL commands.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/01/24/have-finished-a-working-version.html</link>
      <pubDate>Sat, 24 Jan 2026 11:22:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/01/24/have-finished-a-working-version.html</guid>
      <description>&lt;p&gt;Have finished a working version of my game for my niece. It&amp;rsquo;s only the free typing mode for now, which is essentially a basic text editor, with some colour options mapped to keys F1 through to F8. I&amp;rsquo;ve deployed a &lt;a href=&#34;https://flying-start.lmika.games&#34;&gt;browser-based version&lt;/a&gt; of it if you&amp;rsquo;re curious.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260124-112050.png&#34; alt=&#34;Auto-generated description: A digital screen displays the text HELLO WORLD! in colourful letters against a backdrop of silhouetted trees and buildings under a cloudy sky.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Game For Niece — More Art and Cropping Ebitengine Images</title>
      <link>https://lmika.org/2026/01/20/devlog-game-for-niece-more.html</link>
      <pubDate>Tue, 20 Jan 2026 23:22:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/01/20/devlog-game-for-niece-more.html</guid>
      <description>&lt;p&gt;Drew some more artwork and started integrating it into the game. Replaced the previous bus image I got from an image search with one I created myself using Affinity Designer:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260120-223806.png&#34; width=&#34;600&#34; height=&#34;359&#34; alt=&#34;Auto-generated description: A cartoon-style bus is depicted in an illustration with the word BUS in green text above it.&#34;&gt;
&lt;p&gt;It&amp;rsquo;s orange, just like the busses round here, although this one has a plainer livery.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve not integrated the car just yet: I&amp;rsquo;m hoping to finish off the animations for the bus first. But I&amp;rsquo;m so glad I embraced Affinity Designer for this. I&amp;rsquo;m surprise I haven&amp;rsquo;t considered it sooner. The whole image is designed as a whole, then I export each component — body, door, wheels — as a separate PNG and recompose it in the code, giving me the means of animating each component. I&amp;rsquo;d imagine this is a pretty typical workflow: and I&amp;rsquo;m surprise I haven&amp;rsquo;t considered this sooner too.&lt;/p&gt;
&lt;p&gt;And yes, I will admit I did look at AI image generation for this. But I wasn&amp;rsquo;t happy with what it produced, and I wanted each image to have a consistent style. Turns out an &amp;ldquo;elementary vector artist&amp;rdquo; style works perfectly here.&lt;/p&gt;
&lt;p&gt;Anyway, took a bit of time working out how to draw a cropped image in Ebitengine. Spent a little bit of time seeing what was possible with masks. But after looking at the &lt;a href=&#34;https://ebitengine.org/en/examples/tiles.html&#34;&gt;examples&lt;/a&gt;, it looks like using &lt;a href=&#34;https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2#Image.SubImage&#34;&gt;SubImage&lt;/a&gt; for this is viable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;busDoor&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;getSpriteImage&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bus_door.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;render&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;doorDX&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sprBusDoor&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Bounds&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;Dx&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;doorDY&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bp&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;sprBusDoor&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Bounds&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;Dy&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;GeoM&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Translate&lt;/span&gt;(float64(&lt;span style=&#34;color:#a6e22e&#34;&gt;doorDX&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;), &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImage&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;busDoor&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SubImage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;image&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Rect&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;doorDX&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;doorDY&lt;/span&gt;)).(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;), 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImageOptions&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;GeoM&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As for the other scenes I need to add, I plan to work through vehicles and objects first as they seem easier to draw. Hopefully by doing so I gain some practice before I tackle more organic objects like trees and animals. Tackling those feel a little daunting to me: more curves involved.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2026/01/18/well-would-you-look-at.html</link>
      <pubDate>Sun, 18 Jan 2026 10:11:28 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2026/01/18/well-would-you-look-at.html</guid>
      <description>&lt;p&gt;Well, would you look at that. Turns out I can draw… somewhat.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2026/out-20260118-100926.png&#34; width=&#34;600&#34; height=&#34;965&#34; alt=&#34;Auto-generated description: A step-by-step illustration shows the transformation of a simple car sketch into a detailed, coloured car design using digital editing software.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Rendering Outlined Text in Ebitengine</title>
      <link>https://lmika.org/2025/12/28/rendering-outlined-text-in-ebitengine.html</link>
      <pubDate>Sun, 28 Dec 2025 10:34:09 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/28/rendering-outlined-text-in-ebitengine.html</guid>
      <description>&lt;p&gt;For anyone else using the &lt;a href=&#34;https://ebitengine.org&#34;&gt;Ebitengine&lt;/a&gt; that wants to render text as an outline, I&amp;rsquo;ve had had some success using the &lt;a href=&#34;https://github.com/erparts/go-shapes&#34;&gt;shapes&lt;/a&gt; package. The approach that worked for me was to render the text to a separate image, then call &lt;a href=&#34;https://pkg.go.dev/github.com/erparts/go-shapes#Renderer.ApplyOutline&#34;&gt;ApplyOutline&lt;/a&gt; using the screen as the target.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some very unoptimised code demonstrating this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;State&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Draw&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// The image holding the rendered text
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewImage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Bounds&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;Dx&lt;/span&gt;(), &lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Bounds&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;Dy&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// The filled in text colour. Here it&amp;#39;s set to red.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// As far as I can tell, this has no effect on the rendered outline.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;col&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ColorScale&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;col&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Scale&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;GeoM&lt;/span&gt;{}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Draw&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;img&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Outline&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;face&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawOptions&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImageOptions&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImageOptions&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;GeoM&lt;/span&gt;:       &lt;span style=&#34;color:#a6e22e&#34;&gt;pos&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;ColorScale&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;col&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;Filter&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;FilterNearest&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shapes&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewRenderer&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Sets the outline colour to green.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SetColorF32&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Applies the outline. The numerical arguments are the 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// x and y offsets, and the outline width.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ApplyOutline&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;img&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251228-102957.png&#34; width=&#34;600&#34; height=&#34;359&#34; alt=&#34;Auto-generated description: A computer window displays the word OUTLINE in large, green, outlined capital letters on a black background.&#34;&gt;
&lt;p&gt;If you want to render the filled in font alongside the outline, simply draw &lt;code&gt;img&lt;/code&gt; to the screen before applying the outline:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;img&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ebiten&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DrawImageOptions&lt;/span&gt;{})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;shapes&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewRenderer&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SetColorF32&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ApplyOutline&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;screen&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;img&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251228-103014.png&#34; alt=&#34;Auto-generated description: Large text reading FILLED IN &amp; OUTLINED is displayed in bright green with a red outline against a black background.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/12/27/just-placed-the-exit-on.html</link>
      <pubDate>Sat, 27 Dec 2025 10:15:38 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/27/just-placed-the-exit-on.html</guid>
      <description>&lt;p&gt;Just placed the exit on Level 3-2 on my Godot game. The level still needs prettying up but the critical path is now done. I&amp;rsquo;ll think this will be the last level I build. There&amp;rsquo;s still so much work to do: polishing, backdrops, menus, etc. But no more content. Kind of want to put a pin in this one.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251227-101258.png&#34; alt=&#34;Auto-generated description: A 2D platformer game level with block structures, steps, and floating platforms.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/12/25/so-this-is-what-christmas.html</link>
      <pubDate>Thu, 25 Dec 2025 07:29:10 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/25/so-this-is-what-christmas.html</guid>
      <description>&lt;p&gt;So this is what Christmas in winter feels like. 🥶&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251225-072823.png&#34; width=&#34;600&#34; height=&#34;291&#34; alt=&#34;Auto-generated description: A weather forecast for Phillip Island shows possible showers with windy conditions and temperatures ranging from 14 to 18 degrees.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/12/20/trying-out-mb-manager-by.html</link>
      <pubDate>Sat, 20 Dec 2025 14:40:04 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/20/trying-out-mb-manager-by.html</guid>
      <description>&lt;p&gt;Trying out &lt;a href=&#34;https://timapple.com/mbmanager/&#34;&gt;MB Manager&lt;/a&gt; by &lt;a href=&#34;https://micro.blog/timapple&#34;&gt;@timapple&lt;/a&gt;. Not bad. I like how snappy everything is. I also like the option for a serif font. Quite a unique way of reading Micro.blog.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251220-142750.png&#34; width=&#34;600&#34; height=&#34;1303&#34; alt=&#34;Auto-generated description: A social media app&#39;s timeline view displays two user comments and a status update about a podcast app update.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Some Caution on Using ROWIDs for Primary Keys in Sqlite 3</title>
      <link>https://lmika.org/2025/12/18/some-caution-on-using-rowids.html</link>
      <pubDate>Thu, 18 Dec 2025 16:24:21 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/18/some-caution-on-using-rowids.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve noticed that relying on ROWIDs for primary keys in Sqlite 3 tables can result in IDs being reused. If you insert a row that is given a ROWID of 1, delete it, then insert another row, that second row will also be given a ROWID of 1. At first I thought it was just the &lt;a href=&#34;https://pkg.go.dev/modernc.org/sqlite&#34;&gt;Go port I was using&lt;/a&gt;, but I also tried a &lt;a href=&#34;https://github.com/mattn/go-sqlite3&#34;&gt;package that uses the C library&lt;/a&gt; and I observed the same thing. It&amp;rsquo;s reproducable using the &lt;code&gt;sqlite3&lt;/code&gt; CLI tool:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251218-161954.png&#34; width=&#34;600&#34; height=&#34;369&#34; alt=&#34;Auto-generated description: A terminal window displays SQLite commands and outputs, including creating and modifying a table with text values.&#34;&gt;
&lt;p&gt;This shouldn&amp;rsquo;t be an issue normally, but I have been trying to enable &lt;a href=&#34;https://sqlite.org/pragma.html#pragma_foreign_keys&#34;&gt;foreign keys&lt;/a&gt; with cascade deletes in a program that&amp;rsquo;s using Sqlite 3. And maybe I&amp;rsquo;m not enabling them properly, but I&amp;rsquo;ve been finding rows that should&amp;rsquo;ve been deleted via this cascade were showing up in joins with new rows created on the foreign table. I don&amp;rsquo;t know why this is not working, but even so, having IDs that are never reused might be helpful here.&lt;/p&gt;
&lt;p&gt;To avoid this, you can add &lt;code&gt;autoincrement&lt;/code&gt; to the ID field to ensure that IDs are always monotonically increasing:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251218-162010.png&#34; width=&#34;600&#34; height=&#34;369&#34; alt=&#34;Auto-generated description: A terminal window displays commands and output illustrating the creation and modification of a test table in SQLite, including inserting and deleting rows.&#34;&gt;
&lt;p&gt;Apparently, this is &lt;a href=&#34;https://sqlite.org/autoinc.html#:~:text=The%20AUTOINCREMENT%20keyword%20imposes%20extra%20CPU%2C%20memory%2C%20disk%20space%2C%20and%20disk%20I/O%20overhead%20and%20should%20be%20avoided%20if%20not%20strictly%20needed.%20It%20is%20usually%20not%20needed.&#34;&gt;not encouraged&lt;/a&gt; as it adds a bit of overhead to inserting rows. But if the price of unique IDs is a little more memory and CPU usage, for my teeny-tiny database,  it&amp;rsquo;s worth it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/12/12/doom-text-generator-looks-like.html</link>
      <pubDate>Fri, 12 Dec 2025 16:14:45 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/12/doom-text-generator-looks-like.html</guid>
      <description>&lt;p&gt;🛠️ &lt;a href=&#34;https://c.eev.ee/doom-text-generator/&#34;&gt;Doom Text Generator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looks like fun. Giving it a whirl:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/doom-text-generator.png&#34; width=&#34;555&#34; height=&#34;174&#34; alt=&#34;Text in Doom-like pixelated font. Message: Am I asking for too much to want obsidian to add multi-cursor support? (Press Y/N)&#34;&gt;
&lt;p&gt;Via: &lt;a href=&#34;https://blog.martin-haehnel.de/2025/12/11/doom-text-generator/&#34;&gt;Martin Hähnel&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Trying Out Pagefind in Micro.blog</title>
      <link>https://lmika.org/2025/12/09/trying-out-pagefind-in-microblog.html</link>
      <pubDate>Tue, 09 Dec 2025 08:21:24 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/09/trying-out-pagefind-in-microblog.html</guid>
      <description>&lt;p&gt;Trying out &lt;a href=&#34;https://www.manton.org/2025/12/08/pagefind-and-microblog-actions.html&#34;&gt;Pagefind&lt;/a&gt; that was just released for Micro.blog. I find myself using the search on this blog surprisingly often, and while the search plugin has served me well, I&amp;rsquo;m always up for trying something new.&lt;/p&gt;
&lt;p&gt;It took me a bit of time to work out how to get the actual search page. What I did was uninstall the search plugin, create a new &amp;ldquo;Search&amp;rdquo; page, and copied-pasted the &lt;a href=&#34;https://pagefind.app/docs/ui-usage/&#34;&gt;sample UI&lt;/a&gt; on Pagefind&amp;rsquo;s docs. That worked without a hitch. I explored changing some of the &lt;a href=&#34;https://pagefind.app/docs/ui/&#34;&gt;display options&lt;/a&gt;, such as raising the number of results and the size of the snippets. Interestingly, a smaller number of results actually feels nicer. I started with 25 but seeing all those results felt overwhelming (who knew there was so much nonsense written here). I think I&amp;rsquo;ll give 10 a go.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251209-080903.png&#34; width=&#34;600&#34; height=&#34;403&#34; alt=&#34;Auto-generated description: A code snippet featuring a search function and options to include the page in navigation, set page size, and excerpt length is displayed.&#34;&gt;
&lt;p&gt;If I can provide some feedback to Manton, it would be to choose which pages to index. I did a search for &amp;ldquo;Devlog&amp;rdquo; and found that the stats page, category list page, and various pages of the blog list were showing up in the results. It would be nice to configure the index to just the blog posts, maybe also the custom pages, and leave the entry lists out. I am curious to explore the capabilities of Pagefind some more, particularly around faceted search. One thing I&amp;rsquo;ve been thinking of for a while is the ability to use &lt;code&gt;#&lt;/code&gt; to filter based on cataegories, such as &lt;code&gt;#photos birds&lt;/code&gt; to return only the results with the &amp;ldquo;Photos&amp;rdquo; category. I did consider hacking this into the search plugin, but maybe I won&amp;rsquo;t have to.&lt;/p&gt;
&lt;p&gt;All in all, I think I like it. I like how fast the results come through, and although I do prefer the UI of the search plugin, I think that&amp;rsquo;s solvable by tuning this one to my desires (or, just more likely, I&amp;rsquo;ll simply get use to it). I think this is a great addition to Micro.blog.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Project - Level 3-2 And Bobbing Water</title>
      <link>https://lmika.org/2025/12/08/devlog-godot-project-level-and.html</link>
      <pubDate>Mon, 08 Dec 2025 22:32:32 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/12/08/devlog-godot-project-level-and.html</guid>
      <description>&lt;p&gt;I spent a lot of time on distractions recently, so I was a little surprised to find myself wanting to get back to working on that Godot game. The level under design is going okay: it took a few attempts to find the right way to start. Turns out writing down how you want the level to progress helps, like having three &amp;ldquo;acts&amp;rdquo; where the first act consists of some jumping puzzles, then introducing one of the gimmick, than a variant of said gimmick, and so on.&lt;/p&gt;
&lt;p&gt;Anyway, I&amp;rsquo;m working on the second &amp;ldquo;act&amp;rdquo; which will feature a low-power mechanic: there&amp;rsquo;s only so many units of power to energise the various zones, and the player needs to juggle it all to get through the act. Because power is involved, I wanted to have a generator sprite to provide some decoration. This is what I came up with:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251203-221842.png&#34; width=&#34;600&#34; height=&#34;327&#34; alt=&#34;Auto-generated description: A pixelated knight stands next to a large, metal generator in a block-brick environment with blue water below.&#34;&gt;
&lt;p&gt;It&amp;rsquo;s… fine. I did try to get some inspiration by uploaded the tile-set to Google Gemini and asked it to produce something that resembled a generator:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using the tile set images uploaded, please provide some suggested designs for a tile-set representing an electric generator. The suggestion must match the style and colour scheme of the supplied image.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This helped, although what I have here is a fair bit different to what was generated. I may adjust the colours a bit (the simple colours are by design as I want to maintain the palette used by the existing tile-set) and I may need to add some wire decorations and maybe a sign that says &amp;ldquo;Generator&amp;rdquo; to make it clear that&amp;rsquo;s what it is, and that it&amp;rsquo;s not a cannon, as the alt-text generation suggests. We&amp;rsquo;ll see how it goes, I guess.&lt;/p&gt;
&lt;p&gt;One other thing I found myself wanting to add is water that can be raised and lowered when activated. I came up with something from first principals that looks a little like this (the tile layer is hidden to show the &lt;code&gt;Area2D&lt;/code&gt; shapes):&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251208-221530.png&#34; width=&#34;600&#34; height=&#34;226&#34; alt=&#34;Auto-generated description: A 2D platformer game editor displays a character, crates, water sections, and terrain blocks.&#34;&gt;
&lt;p&gt;It consists of is a top-level &lt;code&gt;Node2D&lt;/code&gt; which will raise and lower when triggered. This is done in code, just to allow me to configure the speed and displacement on a case-by-base basis. A child of that is a &lt;code&gt;TileMapLayer&lt;/code&gt; containing the water tiles. It&amp;rsquo;s at Z-index 6, which is just in front of the player so that it looks like it engulfs them if they fall in. Four units below the top is the kill-plane — water is a hazard — and four units below that is the &amp;ldquo;bobbing&amp;rdquo; plane. This is a &lt;code&gt;Node2D&lt;/code&gt; that simply oscillates up and down by 2 units every second. A child of that is an &lt;code&gt;Area2D&lt;/code&gt; solid with allows the player to move through, but will collide with a new crate sprite, which also collides with everything else.&lt;/p&gt;
&lt;p&gt;This has the effect of a water plane in which creates would appear buoyant yet slightly submerged, giving the player somewhere to stand. Activating the water layer will allow it to &amp;ldquo;drain,&amp;rdquo; bringing the crates down to rest on any solids. Activate it again and the water level will rise, catching any crates which will begin floating:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.mov/25293/2025/screencast-water-bobbing/playlist.m3u8&#34; poster=&#34;https://cdn.uploads.micro.blog/25293/2025/frames/1633294-2-a7753f.jpg&#34; width=&#34;1158&#34; height=&#34;720&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s probably better ways to do this, and there are some drawbacks. It can&amp;rsquo;t be bundled into a dedicated scene, so each instance will need to be built manually. And if more than one crate is in the scene, they will bob in unison (although that could probably be alleviated with splitting the bobbing plane into areas with distinct offsets). But for something devised from first principals, I&amp;rsquo;m quite happy with it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools - Image Collections Triage App</title>
      <link>https://lmika.org/2025/11/29/devlog-blogging-tools-image-collections.html</link>
      <pubDate>Sat, 29 Nov 2025 10:54:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/11/29/devlog-blogging-tools-image-collections.html</guid>
      <description>&lt;p&gt;Up until now, I&amp;rsquo;ve been using Blogging Tools&amp;rsquo; in-app notification system to sort images to collections. There were quite a few limitations in doing so: only notifications for the first for images were raised, galleries weren&amp;rsquo;t handled, and you could only select one category per image. I also wanted a way to automatically change the header image in this blog. This was done using the image category notification, but it was pretty basic in that the image was chosen as the header if any category was selected.&lt;/p&gt;
&lt;p&gt;I had ideas of improving all of this but I realised quickly that I needed something more sophisticated to do so. So I added another app to Blogging Tools to do this. I also thought to try out using Antigravity to do this. I was curious to know how well Google Gemini would work with a project that already had an established architecture and coding patterns. I was also curious to know how Antigravity felt when I held a tighter grip of the reigns: I wanted to drive the development work and only turning to the agent when I need something done.&lt;/p&gt;
&lt;p&gt;I started simply enough: I built a basic HTML template for this new app and asked Gemini to prepare a stub handler:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Please create a new handler that services the template &amp;ldquo;imageposts/show.html&amp;rdquo;. Implement it much like the other handlers found in the &amp;ldquo;handlers&amp;rdquo; directory.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I forgot to mention to the agent that this was to be a stub, and it ended up using the existing provider for the gallery app. But it was pretty close to what I wanted. I then asked it to suggest a good HTML entity for opening a page in an external window.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What&amp;rsquo;s a good HTML entity for indicating that a link will open up into an external window/tab?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It suggested &lt;code&gt;↗&lt;/code&gt;, also a good answer. I then went on to build the Go models and wanted to work on the CSS styling next, so I asked Gemini to produce some example data using these models:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Please generate 3 stubbed values for this item slice. Use real example image URLs for the source.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It struggled a little on this one, suggesting a URL that didn&amp;rsquo;t exist. But after pointing this out, it got there in the end. It was at this point that I needed to do some refactoring to some of the existing services, needing to extract out a type that used only the values from another type. Gemini did quite well here too:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the select call. At the moment this is returning a micropub.Item model. I want to make a new model called &amp;ldquo;ImageCollection&amp;rdquo; that uses only the values of this micropub.Item that the code is currently using now. I don&amp;rsquo;t want the micropub.Item changed, but any code that is using this for the purpose of the selected call should include a map to this new model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I guess this is not a novel experience to anyone using Cursor, but what I really liked about this interaction is that I was free to work on other things or move away from the keyboard entirely while the agent was working. This is one annoyance that I have with Claude Code: the exchange pauses and waits for me to approve every change before it proceeds. Having Gemini plan the changes first, then have me approve them once I&amp;rsquo;m back is a much nicer way of working.&lt;/p&gt;
&lt;p&gt;I then turned to the database schema, defining the table schema and SQL queries myself, and asking Gemini to produce the Go code that marshals the data: the so called &amp;ldquo;database providers&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Implement the Go DB provider for DB tables for the queries in &amp;ldquo;imageposts.sql&amp;rdquo;. Do it in a similar style to the other providers found in &amp;ldquo;providers/db&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is where I get the most use out of using coding agents. This code is not hard, but it&amp;rsquo;s super annoying to write. I think it also helps that there&amp;rsquo;s an established code base for the agent to work on. I was a little unhappy with what the agent produced last week, probably because it was required to produce it from scratch and in a way that wasn&amp;rsquo;t how I would write it. But here, probably because the agent had existing patterns to work on, it&amp;rsquo;s producing something that looks a lot better, to my eyes at least.&lt;/p&gt;
&lt;p&gt;It did struggle with the providers a little, choosing to merge an update to two models in a single method. I did try to correct it, but I just had to go in and fix it manually.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We can probably separate SaveImagePost into one that saves the ImagePost, and another that saves ImagePostItems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One other thing I discovered is that getting Gemini to write unit tests is important, as it provides a way for the agent to verify it&amp;rsquo;s code. I approved a change Gemini produced without looking too closely, and if I were to test it myself, it would&amp;rsquo;ve failed. But only after I asked it to produce a unit test did I see it fix the errors introduced in the last interaction. Something to think about in the future.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Might be a good idea to implement a unit test to verify that the DB methods in imageposts.go work. Do this by creating a new db.Provider instance with a temporary file, and verifying that the provider methods of imageposts.go work as expected. Please use testify.Assert to do the asserts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was now time to build the service layer. This is something I wanted to drive myself, occasionally relying on auto-suggestions. Antigravity fell down (ha ha) here a little, producing suggestions that were pretty far from what I wanted. I guess it was lacking the context to know what I actually wanted, which is somewhat understandable as I wasn&amp;rsquo;t giving that context. It also seems to fight with IntelliSence from VS Code sometimes. I occasionally press tab in response to a type suggestion from the index, only to have it produce a completion for some unknown type. I do wish the suggestions use the code index more in general. There&amp;rsquo;ve been more than a few times where the agent suggested types and methods that just didn&amp;rsquo;t exist. Might be something that can be tuned.&lt;/p&gt;
&lt;p&gt;One quality of life improvement I did make was increase the timeout for when suggestions would appear. The default was something like 50 milliseconds, which is pretty short. Bumping it to 1 second helps, but even so they still get in the way. I may bump it a little higher.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251129100529.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: A web interface for blogging tools features options to categorize or set an image of a scenic landscape as a header.&#34;&gt;
&lt;figcaption&gt;First cut of the collection triage screen at the end of day one.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I worked on this feature over the next couple of evenings, occasionally changing tack which required a change in the database schema. One thing I did like while working on this was getting to the point where I could simply ask the agent to update the provider code whenever I did so, and it knew what I meant:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The ImagePost model and table schema now has a PostDate field/column. Please update the providers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I do get the feeling that the user interaction in Antigravity needs to be refined a little. For someone who is willing to do most of the driving, it would be nice if the editor was a little less &amp;ldquo;needy&amp;rdquo;. This is probably more to do with VS Code being the underlying editor: lots of annoying popups and mouse overs, all shouting for my attention: LOOK AT THIS, LOOK AT THIS. But the AI suggestions don&amp;rsquo;t help. Instead of displaying the generated code, disrupting my sense of where everything is, maybe a more subtler approach is warranted: a small indicator beside the cursor or in the margin indicating that a suggestion is present. I may miss it, but who cares? Remember, I&amp;rsquo;m the one driving here.&lt;/p&gt;
&lt;p&gt;Today, I got the feature finished. The way it works is that it will poll the RSS feed of this blog, and for every post that has an image, it will add an entry for me to triage. Each entry consists of a brief summary, plus the images found within the post:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251129100017.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: A menu interface displaying various blogging tools and options for managing images, videos, and text.&#34;&gt;
&lt;figcaption&gt;A new &#39;Sort Image Posts&#39; app, with a badge indicating the number of images to triage.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251129100346.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: Blogging interface featuring a post titled Under the big, grey sky-rail with a small image and options to edit or delete.&#34;&gt;
&lt;figcaption&gt;List of posts with images that need triaging.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Clicking through to the post would list the collections, and I would select the ones I&amp;rsquo;d like the image to be filed in. And this would list all the images of the post, not just the first few. It is all or nothing, unfortunately. There are ways to get the images of the collection, but I couldn&amp;rsquo;t see a way for me to get the collections of an image. I don&amp;rsquo;t think this would be a problem, as Blogging Tools is the only way I actually set the collections of an image.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251129100432.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: A screen displays a blogging tool interface with an image of an elevated roadway and options for categorizing an image post.&#34;&gt;
&lt;figcaption&gt;Selecting a post will list the images found within that post, with the collections they should be added to.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Saving the categories will queue the changes, which will be applied every 5 minutes. A first cut of this feature applied the changes immediately, but the way I imagine using this is that I&amp;rsquo;d go in, triage the images in one go, then leave. Applying all the changes then felt inefficient.&lt;/p&gt;
&lt;p&gt;When it comes to setting the header image, I figured I&amp;rsquo;d add a dedicated collection for that. Every hour a cronjob will query the local database for images that is within this category, and write the URLs to a manifest file in the assets Git repository. A Forgejo Action on that repository runs every night, and will check the date-stamps for the next image to display on the header. If one is found, it will fetch the image, save it as a JPEG with reduced quality and upload to Netlify. Since Netlify flushes the CDN whenever a deployment happens, nothing more needs to be done: no MD5 sum hashes, no adjustment to CSS files. Just update &lt;code&gt;header.jpg&lt;/code&gt; and go:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Example of the manifest file.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;images&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;date&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2025-11-22T13:00:00Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://lmika.org/uploads/2025/pxl-20251120-083552448.jpg&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;crop&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bottom&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;date&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2025-11-26T13:00:00Z&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://lmika.org/uploads/2025/a3c74bfebd.jpg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deployed all these changes to prod and naturally some rework that&amp;rsquo;s needed. First, I completely forgot about galleries. They do come through as image tags in the generated HTML, but I&amp;rsquo;m operating on the source post, and they appear as Hugo short-codes. Fortunately the way I write galleries is pretty standardised now, so I think a regular expression would work here.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;galleryRegexp&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;regexp&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;MustCompile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;`[{]{2}[&amp;lt;][ ]?glightbox.*src=&amp;#34;([^&amp;#34;]+)&amp;#34;.*[&amp;gt;][}]{2}`&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, I also want to fix the layout of the collection checkboxes and hide some options, such as those that for past years. And finally, will turn off the existing image category notification logic.&lt;/p&gt;
&lt;p&gt;Deployed again, and now I just need to wait for the next time I post a photo. I suspect some bug fixes or usability adjustments will come from this, but more on that when that happens.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/11/24/three-nuisance-calls-today-so.html</link>
      <pubDate>Mon, 24 Nov 2025 17:03:04 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/11/24/three-nuisance-calls-today-so.html</guid>
      <description>&lt;p&gt;Three nuisance calls today so now call screening is being turned on. I think this feature deserves a &amp;ldquo;finally.&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/edited-1ed3489e-6567-4e3c-bc5e-a6a3cce2d9614258454133464335246.jpg&#34; width=&#34;359&#34; height=&#34;600&#34; alt=&#34;A mobile interface displays call screening options, including automatic call screening and varying levels of protection from spam.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>First Impressions Of Google Antigravity</title>
      <link>https://lmika.org/2025/11/22/first-impressions-of-google-antigravity.html</link>
      <pubDate>Sat, 22 Nov 2025 14:03:33 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/11/22/first-impressions-of-google-antigravity.html</guid>
      <description>&lt;p&gt;Well, as predicted, I had a go at using Google Antigravity.&lt;/p&gt;
&lt;p&gt;I downloaded last Wednesday to try out the editor and agent. I didn&amp;rsquo;t have a good idea for it so I simply asked it to produce &lt;a href=&#34;https://tools.lmika.app/timestamps/&#34;&gt;an additional tool&lt;/a&gt; for my suite of online tools: one for dealing with timestamps and timezone conversions. I had plans for this one for a while, although they were relatively vague, it seemed like a good test of Gemini as a coding agent.&lt;/p&gt;
&lt;p&gt;I started with a bit of vibe-coding to see how much it could produce on it&amp;rsquo;s own. Here the prompt I used:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the tools located in &amp;ldquo;site&amp;rdquo;. I would like another one made that will process timestamps.&lt;/p&gt;
&lt;p&gt;The tool will consist of two text boxes side-by-side. The text box to the left will be user input: the user will use this to enter timestamps of various types, with one timestamp per line. On the right will be the output. It is not designed to be editable, but the user should be able to scroll, copy, and otherwise get the text from this pane.&lt;/p&gt;
&lt;p&gt;Above the two should be a select picker of operations. Beside that are two radio buttons: &amp;ldquo;UTC&amp;rdquo; and &amp;ldquo;Local&amp;rdquo;. This is for selecting the given timezone, where Local is the browser&amp;rsquo;s local timezone.&lt;/p&gt;
&lt;p&gt;The way this works is that the user will enter a line of input in the text area in the left. When changed, as long as the line of input is valid, the tool will apply the operation and display the result on the same line as the input in the text area on the right. The operation to apply will depend on the picker. Blank lines and any line beginning with &amp;ldquo;#&amp;rdquo; are valid and are to be copied as is. Timestamps are to be displayed in ISO 8601 within the selected timezone.&lt;/p&gt;
&lt;p&gt;The picker should have the following options, which do the following operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Convert from Unix&amp;rdquo;: converts the input in seconds from the Unix epoch into a timestamp on the right.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Convert from Unix Micro&amp;rdquo;: converts the input in milliseconds from the Unix epoch into a timestamp on the right.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;To UTC&amp;rdquo;: converts the input in ISO 8601 in the selected timezone into UTC, regardless of what timezone is chosen. &amp;ldquo;From UTC&amp;rdquo;: converts the input in ISO 8601, which is assumed to be in UTC, into the selected timezone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This should be implemented as a static HTML page with vanilla JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The agent did crash a few times: probably launch day, go-live teething problems. At one point it stopped halfway, and I had to ask it to &amp;ldquo;continue with the implementation plan.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In the end it did manage to produce a decent-looking HTML UI, plus a working JavaScript implementation of what I described. The quality of the generated code was… fine. The JavaScript worked, but it wasn&amp;rsquo;t as neat as something produced by Claude Code, and definitely not as neat as something I would&amp;rsquo;ve written myself (the HTML was fine).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251119-224038.png&#34; width=&#34;600&#34; height=&#34;361&#34; alt=&#34;Auto-generated description: A timestamp converter interface displays UNIX time conversions for specified dates and the current time.&#34;&gt;
&lt;p&gt;I had ideas of going further, so I asked the agent to port the JavaScript to Go and to produce a WSDL target, like many of the other tools.  The agent had no issues doing so.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Please port the main logic defined in &amp;ldquo;main.js&amp;rdquo; to Go targetting WASM, in a new directory located in &amp;ldquo;cmds&amp;rdquo;. You can use the other WASM files found in &amp;ldquo;cmds&amp;rdquo; as a guide.&lt;/p&gt;
&lt;p&gt;In it&amp;rsquo;s place, please change &amp;ldquo;site/timestamps/main.js&amp;rdquo; to be a WASM launcher, much like ther other launchers defined in &amp;ldquo;site/clocks&amp;rdquo; and &amp;ldquo;site/templates&amp;rdquo;. Add the new Go target to the Makefile&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I then &amp;ldquo;started driving&amp;rdquo; by extending the Go code by adding a parser for a potential mini-language. And from this I got to experience Antigravity as a code editor. And it&amp;rsquo;s essentially VS Code, which honestly is not my favourite IDE. In fact, with all the LLM-powered code completion, it&amp;rsquo;s a little closer to Cursor, which I only tried once before. I was turned off by Cursor due to all the suggestions getting in the way, and Antigravity is a bit like that too. I kind of wish there&amp;rsquo;s more prioritising of suggestions: those that fix an compile error, or can be inferred by a refactor should have more weight than those that lack the context to be more than just mere guesses. The latter is rarely what I want, and I usually have to leave the home row to reach for the Escape, or I&amp;rsquo;ve accidentally pressed tab and have to back-out of the unwanted insert. Maybe that&amp;rsquo;s something that I can adjust: I haven&amp;rsquo;t looked, but I would prefer it if it just did nothing if it couldn&amp;rsquo;t be sure of what I want.&lt;/p&gt;
&lt;p&gt;I also tried the testing agent, where it spun up Chrome and drove it to test the UI. The agent did have some issues launching the service — probably because I had the dev server already running — but it did successfully test the UI out. The tests were unsuccessful, and I had to go in and debug it, but hey, that&amp;rsquo;s something, I guess.&lt;/p&gt;
&lt;p&gt;So, would I use Antigravity again? I mean, it&amp;rsquo;s a decent IDE (or maybe it&amp;rsquo;s better to say that it&amp;rsquo;s built on a decent IDE), and I think the agents work quite well. But I probably wouldn&amp;rsquo;t use it again, mainly because it&amp;rsquo;s now how I like to interact with agents. I&amp;rsquo;ve grown accustom to how Claude Code works, where the agent takes instruction from you on your terms. Otherwise it stays out of the way. And to be fair, that&amp;rsquo;s sort of how the agent in Antigravity works too. If they just toned down the suggestions in the editor itself, it may be something for me in the future. We&amp;rsquo;ll see.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Laying The Groundwork For Dynamic Header Images</title>
      <link>https://lmika.org/2025/11/08/devlog-laying-the-groundwork-for.html</link>
      <pubDate>Sat, 08 Nov 2025 12:11:26 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/11/08/devlog-laying-the-groundwork-for.html</guid>
      <description>&lt;p&gt;Making some changes to the Card theme I&amp;rsquo;m using for this blog. First think I&amp;rsquo;m considering is a banner image, similar to the one in Scripting News. And like Scripting News, I&amp;rsquo;m hoping for the image to change occasionally. I&amp;rsquo;d like the change to happen when the blog is being built, and in order to do this, I need a way to configure this value. I&amp;rsquo;m hoping to use Blogging Tools to do this, but to actually make use of these values, I&amp;rsquo;m hoping to use &lt;a href=&#34;https://gohugo.io/methods/resource/data/#article&#34;&gt;Hugo&amp;rsquo;s resource data&lt;/a&gt; methods.&lt;/p&gt;
&lt;p&gt;The idea is to setup an assets project which will be hosted on Netlify. When I want to change anything in it, Blogging Tools will push new changes via Git, which will run a pipeline that will deploy the updates to Netlify. Then, next time I add a blog post, Hugo will pick up these data fields and use it to build the site, probably by setting an attribute that will itself be picked by CSS&amp;rsquo;s newly improved &lt;a href=&#34;**https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/attr&#34;&gt;attr()&lt;/a&gt; function to set the banner background.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/assets-lmika-org.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;a9d076a666de56db96237c1af78f6cc3&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/assets-lmika-org.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;assets-lmika-org.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;This will start off as simple data values, stored in JSON, which will include things like the current banner header. One of the reasons why I&amp;rsquo;m keeping the actual data separate is that I don&amp;rsquo;t want any issues with Blogging Tools from impacting my ability to publish blog posts, and I have more confidence in Netlify&amp;rsquo;s ability to keep a service up than I do, especially as a company that is getting paid to do so.&lt;/p&gt;
&lt;p&gt;Being a &amp;ldquo;backend guy&amp;rdquo;, I&amp;rsquo;ll start on the Netlify and Blogging Tools side of things first. Actually, I&amp;rsquo;ll start on the asset repo first, just so I can verify that Micro.blog&amp;rsquo;s Hugo instance can pull data like this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve added the new Git repo and got it publishing to Netlify on commit to main. At the moment there&amp;rsquo;s a single JSON file, &lt;code&gt;/site/data.json&lt;/code&gt; that has a test message. The goal is now to get that onto my test blog and included in the Hugo template on build. Naturally, because Hugo loves changing their public API so often, I need to see how this is done in Hugo 0.117 as it seems to be completely different in Hugo 0.141, the current version as of this writing.&lt;/p&gt;
&lt;p&gt;So, checking out the Hugo source code and doing a &lt;code&gt;find&lt;/code&gt;/&lt;code&gt;grep&lt;/code&gt; search to find the relevant technique. Turn&amp;rsquo;s out the way to do this in Hugo 0.117.0 is to use the &lt;code&gt;getJSON&lt;/code&gt; function. &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/release-0.117.0/docs/content/en/templates/data-templates.md&#34;&gt;Here&amp;rsquo;s a link to the docs&lt;/a&gt; for anyone else who&amp;rsquo;s interested.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ $siteData := getJSON &amp;#34;https://assets.lmika.org/data/site.json&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;My data is {{$siteData.data}}&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, that seams to work:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-102005.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;a9d076a666de56db96237c1af78f6cc3&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-102005.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;out-20251108-102005.png&#34; 
        
  /&gt;
&lt;/a&gt;


So, the next thing to do is to add a banner image. I&amp;rsquo;m thinking of a slightly washed out version of the image, with a subtle gradient to the body housing the cards.  I had to adjust the header template a little to move the &lt;code&gt;site-header&lt;/code&gt; to a wrapping &lt;code&gt;div&lt;/code&gt;. This allowed me to add new CSS to allow the header to span the entire width of the page. It&amp;rsquo;s really fortunate that the Card theme styles the CSS classes, and not the HTML elements themselves. It also allowed me to add a new HTML element with a &lt;code&gt;header-end&lt;/code&gt; class to act as the gradient blend from the backing image to the regular background colour:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Template: layouts/partials/header.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;site-header&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- The original content of &amp;#39;header&amp;#39; --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;header-end&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that I could restyle the header:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/** Header */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* The body had a 10px padding which I had to disable to allow
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     the header to blend all the way to the client sides. */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;page-content&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;footer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;site-footer&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* Disabling the 10px padding on the body meant I had to move
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     the padding here.*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* Using &amp;#39;background-blend-mode&amp;#39; to mix the header image with the background
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     colour. We&amp;#39;re using &amp;#39;color-mix&amp;#39; to make the background color slighty
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     transparent to do this. */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;color-mix&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    in srgb, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.75&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-image&lt;/span&gt;: url(&lt;span style=&#34;color:#e6db74&#34;&gt;https://lmika.org/uploads/2025/c1745b14d3.jpg&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-blend-mode&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;screen&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-size&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;cover&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-position&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;header-end&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* A small gradient from transparent to a fully opaque background colour
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     allows a smooth transition from the header to the body. */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background&lt;/span&gt;: linear-gradient(&lt;span style=&#34;color:#ae81ff&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;deg&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;) &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Various links to MDN documentation covering all these changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/background-blend-mode&#34;&gt;background-blend-mode&lt;/a&gt; to blend the header image with the background colour. This is to wash it out some so that the menu items are not illegible.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix&#34;&gt;color-mix&lt;/a&gt; to set the transparency on the background colour.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/gradient/linear-gradient&#34;&gt;linear-gradient&lt;/a&gt; to generate a linear gradient.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-110434.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;a9d076a666de56db96237c1af78f6cc3&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-110434.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;out-20251108-110434.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Oh, and I naturally forgot dark mode, so added a new style to darken the image in that mode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@&lt;span style=&#34;color:#66d9ef&#34;&gt;media&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;prefers-color-scheme&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;dark&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;color-mix&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      in srgb, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;background-blend-mode&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;darken&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-111245.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;a9d076a666de56db96237c1af78f6cc3&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-111245.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;out-20251108-111245.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Okay, the next thing is to make the image dynamic. I want to try and use CSS &lt;code&gt;attr()&lt;/code&gt;  to do this. Basically the idea is that when the template is built, the image URL is pulled from the data and sent to the CSS via a HTML attribute. Here&amp;rsquo;s the new header template&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Template: layouts/partials/header.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ $siteData := getJSON &amp;#34;https://assets.lmika.org/data/site.json&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data-background-url&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;{{ $siteData.header.imageUrl }}&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the updated CSS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-image&lt;/span&gt;: url(&lt;span style=&#34;color:#e6db74&#34;&gt;attr(data-background-url raw-string&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ah, that didn&amp;rsquo;t work. Apparently there are &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/attr#limitations_and_security&#34;&gt;some restrictions on attr&lt;/a&gt; which could potentially result in security issues. So, plan B is to simply set the &lt;code&gt;background-image&lt;/code&gt; in a &lt;code&gt;style&lt;/code&gt; a style attribute:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Template: layouts/partials/header.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ $siteData := getJSON &amp;#34;https://assets.lmika.org/data/site.json&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;background-image: url({{ $siteData.header.imageUrl }})&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, that&amp;rsquo;s working. Now the final test: can I make it dynamic? I&amp;rsquo;ll change the URL to another image:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;header&amp;#34;&lt;/span&gt;:{&lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;imageUrl&amp;#34;&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://lmika.org/uploads/2025/pxl-20251105-200617867.jpg&amp;#34;&lt;/span&gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And publish a new post to rebuild the template. Let&amp;rsquo;s have a look:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-114024.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;a9d076a666de56db96237c1af78f6cc3&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251108-114024.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;out-20251108-114024.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Awesome, that&amp;rsquo;s working. Now to apply it to the actual blog. I did make one last change: the &lt;code&gt;header&lt;/code&gt; attribute is used for the header of posts too, so I had to add a class — &lt;code&gt;site-header-wrapper&lt;/code&gt; — to only select the site header. But yeah, it looks good. I like it. For reference, here&amp;rsquo;s the final template changes I&amp;rsquo;ve made:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- Template: layouts/partials/header.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ $siteData := getJSON &amp;#34;https://assets.lmika.org/data/site.json&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;site-header-wrapper&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;background-image: url({{ $siteData.header.imageUrl }})&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;site-header&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;!-- The original content of &amp;#39;header&amp;#39; --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;header-end&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the final CSS changes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/** Header */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;body&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;page-content&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;footer&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;site-footer&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;site-header-wrapper&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;color-mix&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    in srgb, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.75&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-blend-mode&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;screen&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-size&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;cover&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background-position&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@&lt;span style=&#34;color:#66d9ef&#34;&gt;media&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;prefers-color-scheme&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;dark&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;header&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;site-header-wrapper&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;background-color&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;color-mix&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      in srgb, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;background-blend-mode&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;darken&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;header-end&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* A small gradient from transparent to a fully opaque background colour
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;     allows a smooth transition from the header to the body. */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;px&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;background&lt;/span&gt;: linear-gradient(&lt;span style=&#34;color:#ae81ff&#34;&gt;180&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;deg&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgba(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;body&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;background&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;color&lt;/span&gt;) &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I didn&amp;rsquo;t have time to integrate this with Blogging Tools, but the groundwork has now been laid for that now.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Dynamo Browse - Item View Annotations and Asynchronous Tasks</title>
      <link>https://lmika.org/2025/11/04/devlog-dynamo-browse-item-view.html</link>
      <pubDate>Tue, 04 Nov 2025 21:42:40 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/11/04/devlog-dynamo-browse-item-view.html</guid>
      <description>&lt;p&gt;The laundry list of things to do in Dynamo Browse has grown over the last week, as I find myself wanting using it and wanting more from it. I&amp;rsquo;ve knocked off many of the small ones: fixing bugs, making it easier to get the first item from a result set. The time has come to tackle some of the larger ones.&lt;/p&gt;
&lt;p&gt;The first is the ability to annotate fields in the item view, as in add additional information to the right of the value. I would personally find that useful for describing the value of an other table using it&amp;rsquo;s ID. But the point is that it can be anything. I&amp;rsquo;d like users to add these annotations via extensions, using UCL. The way I&amp;rsquo;m thinking of doing this is by &amp;ldquo;installing&amp;rdquo; a field annotator, something along the lines as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ui:add-item-annotator { |rs attr_path| 
    return &amp;#34;&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will add an annotator which will take a result set, and a path to an attribute. It will return the annotated value. We&amp;rsquo;ll see how well this will work, but first I&amp;rsquo;ll need to build the Go types. Rendering the item view is done using an &lt;code&gt;ItemRenderer&lt;/code&gt; which basically walks through the attributes of an item and renders it as a table. I think I&amp;rsquo;ll change this to take an item annotation. I also realised I need a type to represent the attribute path. I think a simple linked list would work here. Here&amp;rsquo;s what I got:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AttrPathNode&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Index&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;IsIndex&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Parent&lt;/span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;AttrPathNode&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The trouble is representing both string attribute names and numerical indicies in the same type. Go doesn&amp;rsquo;t have a great way of doing this, so I&amp;rsquo;ve gone with something simple and added boolean which would be true if the node is actually an integer index to a list or set.&lt;/p&gt;
&lt;p&gt;Actually, that may not be necessary. Poking around the renderer code, I found this type used to represent the sub items of an item:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SubItem&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Key&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Value&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Renderer&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So I can simply use strings for the key. Good to know. Okay, added a test annotator and gave it a quick test:
&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.37.46.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8765091e77c1a5bd3340e2f3fa6a373e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.37.46.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-11-03 at 20.37.46.png&#34; 
        
  /&gt;
&lt;/a&gt;


Not bad, although found some glaring issues. It would be nice if the annotation was closer to the value. And laying out the annotations in a dedicated column will cause the entire column to shift as I move through the items. So instead of making it a separate column, I&amp;rsquo;ll try simply concatenating it to the end of the value. I&amp;rsquo;ll also apply the meta-info styling to dim the text a little:
&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.39.24.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8765091e77c1a5bd3340e2f3fa6a373e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.39.24.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-11-03 at 20.39.24.png&#34; 
        
  /&gt;
&lt;/a&gt;


That&amp;rsquo;s much better. I&amp;rsquo;m banking on only a few attributes having annotations, so I&amp;rsquo;m hoping it won&amp;rsquo;t be as busy as it looks here.&lt;/p&gt;
&lt;p&gt;Now to consider the UCL integration. Hmm, how am I going to connect the two? As expected, injecting the annotation directly into &lt;code&gt;ItemRenderer&lt;/code&gt; would introduce a dependency loop. What I could do instead is add a setter allowing the caller of the &lt;code&gt;ItemRenderer&lt;/code&gt; to change the annotation on the fly, and inject the &lt;code&gt;ItemRenderer&lt;/code&gt; as a dependency of the command controller.&lt;/p&gt;
&lt;p&gt;Implemented this, and it was actually easier than I thought.  Now to test this. I settled on the following UCL command:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ui:set-item-annotator { |rs item path| 
    &amp;#34;annotation&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rs&lt;/code&gt; is the result set&lt;/li&gt;
&lt;li&gt;&lt;code&gt;item&lt;/code&gt; is the item being rendered in the view&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt; is the attribute path, represented as a list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attribute path is a new proxy, just to reduce the amount of memory copying between Go and UCL. I kept the same list index semantics, where &amp;gt; 0 starts from the left, and &amp;lt; 0 starts from the right, but it was a little mind bending to reverse this for a linked list.&lt;/p&gt;
&lt;p&gt;A simple annotator which returns the currently displayed item value as an annotation can be implemented as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ui:set-item-annotator { |rs item path|
    $item.($path.(-1))
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here it is in action:
&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.41.25.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8765091e77c1a5bd3340e2f3fa6a373e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-20.41.25.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-11-03 at 20.41.25.png&#34; 
        
  /&gt;
&lt;/a&gt;


Brilliant, it&amp;rsquo;s working! Naturally as commands are simple UCL statements, this could be entered on the command line.&lt;/p&gt;
&lt;p&gt;One thing to consider is that unlike normal commands, the UCL block rendering the annotation is running on the UI thread. Not sure I like this, but the amount of effort required to change this would be significant. I guess I&amp;rsquo;ll just tell everyone to keep annotation rendering fast. And as it&amp;rsquo;s currently implement, it is reasonably fast. Or more accurately, it&amp;rsquo;s not noticeably slow, which is good enough.&lt;/p&gt;
&lt;p&gt;The next feature to add to Dynamo Browse is a way to asynchronously schedule blocks. I think it may be worth tapping into the existing command looper in some way.&lt;/p&gt;
&lt;p&gt;Commands in Dynamo Browse are invoked on a dedicated goroutine. Calling &lt;code&gt;execute()&lt;/code&gt; will attempt to send a command via a channel. If that fails, &lt;code&gt;execute()&lt;/code&gt; will return an error indicating that a command is currently running. This keeps running UCL code off the UI thread, and it also makes it possible to implement commands like &lt;code&gt;ui:prompt&lt;/code&gt; synchronously from the UCL code&amp;rsquo;s perspective, when in reality it pauses this goroutine and sends a message to the UI with a callback to resume the thread:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;uiModule&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;uiPrompt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ucl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CallArgs&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;any&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;) {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prompt&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;args&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Bind&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;prompt&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;resChan&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make(&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cancelChan&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make(&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt;{})  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;commandctrl&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PostMsg&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;events&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PromptForInputMsg&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;Prompt&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;prompt&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;OnDone&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;tea&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Msg&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;resChan&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;OnCancel&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;tea&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Msg&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e22e&#34;&gt;cancelChan&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt;{}{}  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }()  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;resChan&lt;/span&gt;:  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cancelChan&lt;/span&gt;:  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Done&lt;/span&gt;():  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Err&lt;/span&gt;()  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So tapping into this event loop would be nice. But how does one do so?&lt;/p&gt;
&lt;p&gt;I think, probably the simplest way to do so is with buffered channels. These are bounded, meaning that there will be an upper limit to the number of pending tasks, but maybe that&amp;rsquo;s not a bad thing. Having too many pending tasks crowding out the user&amp;rsquo;s ability to run commands will probably make for a poor user experience. So let&amp;rsquo;s set the limit to something quite generous, say 50, and integrate it into the event loop:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdChan&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmdChan&lt;/span&gt;:  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ExecuteAndWait&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;cmdChan&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;cmd&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;postMessage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;events&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;))  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;postMessage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;events&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;StatusMsg&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;res&lt;/span&gt;)))  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;execCtx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;requestRefresh&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;postMessage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;events&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResultSetUpdated&lt;/span&gt;{})  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// New code here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;task&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;pendingTaskChan&lt;/span&gt;:  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;task&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;task&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;postMessage&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;events&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Error&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;))  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now for the UCL API. I have an idea of adding a new &lt;code&gt;async&lt;/code&gt; package for this, which will provide commands for running things asynchronously. It&amp;rsquo;s simplest command would be &lt;code&gt;async:do&lt;/code&gt;, which will schedule a block when the command loop is free. So invoking the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ui:command testasync {  
    echo &amp;#34;This&amp;#34;
    async:do {
        echo &amp;#34;Other&amp;#34;
        async:do { echo &amp;#34;Sierra&amp;#34; }
    }
    async:do {
        echo &amp;#34;Romeo&amp;#34;
    }
    echo &amp;#34;That&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Should display:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;This
That
Other
Romeo
Sierra
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the logs. Trying it out and here&amp;rsquo;s how it looks:
&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-21.31.42.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8765091e77c1a5bd3340e2f3fa6a373e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-21.31.42.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-11-03 at 21.31.42.png&#34; 
        
  /&gt;
&lt;/a&gt;


Okay, that&amp;rsquo;s pretty good. Now to build on this. The next thing to add is a command that will schedule tasks in the future, similar to &lt;code&gt;window.setTimeout()&lt;/code&gt;. For this, I plan to use the &lt;a href=&#34;https://github.com/go-co-op/gocron&#34;&gt;gocron&lt;/a&gt; package. Lots there, but for my purpose, I plan to use the &lt;a href=&#34;https://pkg.go.dev/github.com/go-co-op/gocron/v2@v2.17.0#OneTimeJob&#34;&gt;OneTimeJob&lt;/a&gt; type. It does feel like bringing in a crane to lift an empty wood pallet, but the alternative is building my own scheduler (or getting AI to vibe-code one) using a heap. Maybe something for later, but I think this is fine for now.&lt;/p&gt;
&lt;p&gt;To make use of this, I&amp;rsquo;ll add &lt;code&gt;async:in&lt;/code&gt;, which takes a timeout in seconds, and a block to run. To test this, I&amp;rsquo;ll do the classic &amp;ldquo;count down&amp;rdquo; tests:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;proc countdown { |from|
    if (le $from 0) {
        echo &amp;#34;Blast off!!&amp;#34;
        return
    }
    echo &amp;#34;$from&amp;#34;
    async:in 1 { countdown (sub $from 1) }
}

ui:testasync {
    countdown 10
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here it is in action:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-03-at-22.12.51.mp4&#34; poster=&#34;https://lmika.org/uploads/2025/72bad33388.png&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;One last thing to add to async, the ability to run a query in the background and execute a block once the results are available, keeping long running queries off the UCL goroutine. I think for this I will add another thread-pool which will execute up to two queries in the background, then schedule a task to run once the results are ready.&lt;/p&gt;
&lt;p&gt;The API for running queries is pretty well established, to the point that I&amp;rsquo;ve actually got a helper functions which deal with the UCL side of things, so it&amp;rsquo;s just a matter of implementing an asynchronous version of this. I&amp;rsquo;m thinking of an interface along the following lines:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;async:query &amp;lt;query&amp;gt; { |rs|
    # do something with the result-set $rs
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It should also support keyword arguments too. Unfortunately, UCL requires keyword arguments to be placed after the positional arguments, so they would need to be placed after the block, which is a little yucky.&lt;/p&gt;
&lt;p&gt;Implemented the code, now for the test. This one&amp;rsquo;s a little contrived. What it does is asynchronously run a query on startup, counting the number of opened and closed offices in the &lt;code&gt;business-addresses&lt;/code&gt; table. Once those results are available, it will install a new column annotation, which will display the count to the right of the &lt;code&gt;officeOpened&lt;/code&gt; fields.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;proc _prep_officeCount {
    openedOffices = 0
    closedOffices = 0

    async:query &amp;#39;officeOpened=true&amp;#39; { |rs|
        openedOffices = len $rs
        async:query &amp;#39;officeOpened=false&amp;#39; { |rs|
            closedOffices = len $rs

            ui:set-item-annotator { |rs item path|
                if (eq $path.(-1) &amp;#34;officeOpened&amp;#34;) {
                    if $item.officeOpened {
                        &amp;#34;Count = ${openedOffices}&amp;#34;
                    } else {
                        &amp;#34;Count = ${closedOffices}&amp;#34;
                    }
                } else {
                    &amp;#34;&amp;#34;
                }
            }
        } -table business-addresses
    } -table business-addresses
}

_prep_officeCount
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is contrived, but I&amp;rsquo;m hoping to use this in a real setting where I annotate ID fields with a value I retrieve from another table, so a use case like this is pretty close to why I built this feature. Because the queries are running in the background, the goal is to avoid blocking the UI thread, but since this runs on startup before the user selects a table, the table needs to be specified as part of the call. I may have to have a think of how I am to fix that.&lt;/p&gt;
&lt;p&gt;But here it is working. Note the &lt;code&gt;Count = 244&lt;/code&gt; next to the &lt;code&gt;officeOpened&lt;/code&gt; field:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-04-at-13.49.19.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8765091e77c1a5bd3340e2f3fa6a373e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-11-04-at-13.49.19.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-11-04 at 13.49.19.png&#34; 
        
  /&gt;
&lt;/a&gt;


Excellent. The next thing to do is try this out in a real setting. I finished off making a proper goroutine pool for the task that actually runs the query — which I&amp;rsquo;m calling the &amp;ldquo;aux task pool&amp;rdquo; — and a few other changes, like making the meta information a little easier to see.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - Level 3-2 and a Rotating Platform</title>
      <link>https://lmika.org/2025/10/28/devlog-godot-game-level-and.html</link>
      <pubDate>Tue, 28 Oct 2025 22:19:28 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/28/devlog-godot-game-level-and.html</guid>
      <description>&lt;p&gt;Okay, time to start level 3-2. This, like level 3-1, is in the mountainous regions. Except this time, the player will be a little higher. So that means jagged platforms, more verticality, lots of gaps: a real sense that care of where one steps must be taken.&lt;/p&gt;
&lt;p&gt;Starting with some platforming to give this sense: a few narrow safe areas, separated by pits, leading into an area with platforms.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-22-at-21.23.18.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-22-at-21.23.18.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-22 at 21.23.18.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now, I have two choices of mechanics here: I can have the rotating platform, or I can have an area of the tile map fall away when the player lands on it. I think I prefer the rotating platform for the sole reason that adding the falling away platform may be a little overwhelming for the player.&lt;/p&gt;
&lt;p&gt;Actually, no. Danger and instability is the feeling I want to go for here. So I&amp;rsquo;ll add the falling platform, followed by a large safe area where the player can see the rotating player. Also, since this is a new mechanic, it&amp;rsquo;s only right that the player should play with it in a safe area first.&lt;/p&gt;
&lt;p&gt;Okay, I&amp;rsquo;ve added the falling platform. Well to be honest, I designated an area of the field where the falling platform will go. This leads into a large safe area which would be an ideal place to introduce the rotating platforms I have in mind.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m thinking of a scene with an &lt;code&gt;AnimationPlayer&lt;/code&gt; that will do the actual rotation animation: as in rotate the sprites from 0° to 90°. The question is, do I want the entire sequence timed using the &lt;code&gt;AnimationPlayer&lt;/code&gt;? As in, have a single animation that will rotate the platform all 4 times, with gaps between each stage? Or should I stick with the &lt;code&gt;AnimationPlayer&lt;/code&gt; simply animating a 90° rotation, and having another thing timing the wait time between stages?&lt;/p&gt;
&lt;p&gt;I think I prefer the latter. That way, I have more control over the pause between stage rotations. I can do things like disable it, or making it configurable. All desirable features.&lt;/p&gt;
&lt;p&gt;I guess the next question then is whether I want the &lt;code&gt;AnimationPlayer&lt;/code&gt; at all. Wouldn&amp;rsquo;t it be more flexible to do the animation in code? Probably, but I&amp;rsquo;m not sure I want that flexibility, at least not yet. Maybe in time it would be needed, but I think for now, I&amp;rsquo;ll just stick with the &lt;code&gt;AnimationPlayer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s start building out the animation. I think I&amp;rsquo;ll start with four animations, each one rotate the platform from θ to θ+90°, just from different starting positions. I&amp;rsquo;ll use names that include the start and end angles, like &lt;code&gt;rotate_0_to_90&lt;/code&gt;, to allow for bidirectional rotation, should I decided to add that. Angle 0° will be up, and rotation will be clockwise. I think each rotation will be 0.5 seconds at speed 1, just so I have a consistent speed control. I can adjust the speed of the overall playback on the &lt;code&gt;AnimationPlayer&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-25-at-16.22.23.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-25-at-16.22.23.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-25 at 16.22.23.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Okay, animations made. Now to think of collision. I don&amp;rsquo;t like the idea of a collision box rotating along with the platforms. I&amp;rsquo;m not too certain about how well this plays with the physics engine, and even if there were no issues, non-orthogonal geometry is just not a thing in this game. So what I&amp;rsquo;m thinking of doing is adding two collision boxes, one horizontal and one vertical, and just toggling between the two as the platform moves into position. While the platform is rotating, both boxes will be turned off.&lt;/p&gt;
&lt;p&gt;Ooh, I didn&amp;rsquo;t know the collision layer could be added as a animatable track. That&amp;rsquo;s pretty cool.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-25-at-16.33.27.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-25-at-16.33.27.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-25 at 16.33.27.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now to add a &lt;code&gt;Timer&lt;/code&gt; which will actually dictate the cadence. The included code is nice and neat: basically take the current rotation of the sprites in degrees, add 90 to it, and produce a string which will become the next animation to play:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-gdscript&#34; data-lang=&#34;gdscript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; _on_timer_timeout() &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; void:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; _next_animation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;play(next)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; _next_animation() &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; rot &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;int&lt;/span&gt;(sprites&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;rotation &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;180&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; PI) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;360&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rotate_&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;_to_&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; [rot, rot &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Added it to a test level, and I had to speed up the &lt;code&gt;AnimationPlayer&lt;/code&gt; to 3x normal speed, just so that the movement is snappy enough to justify the loss of collision during the rotation. It may need further adjusting a little later, but so far works pretty well. Although I do think I need to telegraph when the platform is about to move.&lt;/p&gt;
&lt;p&gt;Oh, I remember how I&amp;rsquo;m going to telegraph to the player when the platform is about to rotate! It&amp;rsquo;s to be an indicator panel mounted at the front that will rotate slowly on it&amp;rsquo;s own. When it reaches a new orientation it will pause, then the platform behind it will rotate.&lt;/p&gt;
&lt;p&gt;Okay, new sprite. How the heck did I choose a canvas size with non-even dimensions and still end up with a sprite that doesn&amp;rsquo;t have a definitive centre line? 🤦&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.21.15.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.21.15.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-28 at 21.21.15.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Turns out it was how I was removing the upper and lower circles. I was using the eraser for that, and I didn&amp;rsquo;t position it properly.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.24.05.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.24.05.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-28 at 21.24.05.png&#34; 
        
  /&gt;
&lt;/a&gt;


Okay, here&amp;rsquo;s the sprite:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.29.07.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.29.07.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-28 at 21.29.07.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now to think about animating it. I&amp;rsquo;m wondering if it would be possible to simply add it to the same animation player as the platform. The thing is, I would like them to be somewhat independent. The indicator should move on it&amp;rsquo;s own accord, even when the platform sprite is rotating to a new orientation. I would also like the indicator to take on the platform rotation cadence, sending it a signal when it reaches a stop (this will remove the need for the &lt;code&gt;Timer&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Can I do that all in a single scene though? Maybe with two animation players? I&amp;rsquo;ll try that and see how viable it is.&lt;/p&gt;
&lt;p&gt;Okay, just tried that, and yes, using two &lt;code&gt;AnimationPlayers&lt;/code&gt; is viable. Now to prepare the animation. The current rotation period is 3 seconds so I&amp;rsquo;ll start with a 6 second animation that would just rotate the indicator over 180° with a slight pause halfway to trigger the platform. One good thing about the indicator&amp;rsquo;s symmetry is that one animation can cover the full rotation. Although be sure to change the loop wrap mode to &amp;ldquo;Clamp&amp;rdquo;, otherwise you&amp;rsquo;ll see the sprite spin round to 0° when the animation repeats.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.58.34.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;8eeed4ab2dca5262f0cb22b195a74537&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.58.34.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-28 at 21.58.34.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the full thing in action. And yeah, adding the indicator was pretty crucial in the end. The platform wouldn&amp;rsquo;t have been fair otherwise.&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-28-at-21.56.12.mp4&#34; poster=&#34;https://lmika.org/uploads/2025/12f0aa11e2.png&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/27/discovered-that-turning-off-convert.html</link>
      <pubDate>Mon, 27 Oct 2025 12:56:16 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/27/discovered-that-turning-off-convert.html</guid>
      <description>&lt;p&gt;Discovered that turning off &amp;ldquo;Convert pasted HTML to Markdown&amp;rdquo; in Obsidian has eliminated the need to select &lt;em&gt;Edit → Paste and Match Style&lt;/em&gt; when pasting things from the terminal to a note. So glad this setting exists. One more of those things I wished Obsidian had, and sure enough… 🫲 🫱&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251027-102635.png&#34; width=&#34;600&#34; height=&#34;398&#34; alt=&#34;Auto-generated description: A settings panel displays various text editor options related to spellcheck, language preferences, and Markdown conversion features.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/25/anyone-remember-jquery-mobile-looks.html</link>
      <pubDate>Sat, 25 Oct 2025 09:11:35 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/25/anyone-remember-jquery-mobile-looks.html</guid>
      <description>&lt;p&gt;Anyone remember jQuery Mobile? Looks to be live and well on Tram Tracker. First time I actually noticed it, as I usually don&amp;rsquo;t have a need to visit the website.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/d58f792712.png&#34; width=&#34;432&#34; height=&#34;600&#34; alt=&#34;A webpage displays a list of tram routes and destinations, including South Melbourne Beach, East Coburg, East Malvern, and others.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/24/my-eye-has-been-wandering.html</link>
      <pubDate>Fri, 24 Oct 2025 21:14:35 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/24/my-eye-has-been-wandering.html</guid>
      <description>&lt;p&gt;My eye has been wandering towards the &lt;a href=&#34;https://github.com/ericgregorich/micro-blog-cards-theme&#34;&gt;Cards Theme&lt;/a&gt; of late. It was enough of a pull that I decided, after a couple of &lt;a href=&#34;https://en.wikipedia.org/wiki/Schooner_(glass)&#34;&gt;schooners&lt;/a&gt; this evening, to switch this blog over to it. The Mythos theme is good, and the changes I made to the index page to display posts in a similar way to Scripting News was an interesting experiment, but I don&amp;rsquo;t think it fit the type of site I wanted.  I&amp;rsquo;ll give the card theme a try and see if it&amp;rsquo;s a better fit for me.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251024-211714.png&#34; alt=&#34;Auto-generated description: A blog post page featuring one and a bit posts styled like a physical index card, along with timestamps for each entry.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/22/whew-made-it-home-a.html</link>
      <pubDate>Wed, 22 Oct 2025 17:45:19 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/22/whew-made-it-home-a.html</guid>
      <description>&lt;p&gt;Whew! Made it home. A bit of a delay but given the strong winds, it&amp;rsquo;s fortunate that the commute wasn&amp;rsquo;t longer than it was. Could&amp;rsquo;ve been much worse. Good thing that large tree branch didn&amp;rsquo;t fall on the train line (it was close enough to brush against the side of the train).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/f754e83e00.png&#34; width=&#34;516&#34; height=&#34;600&#34; alt=&#34;Service changes indicate disruptions on October 22 due to equipment failure in the Southern Cross area and a fallen tree branch affecting certain train routes and stops.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - Working On Backdrops</title>
      <link>https://lmika.org/2025/10/20/devlog-godot-game-working-on.html</link>
      <pubDate>Mon, 20 Oct 2025 22:01:17 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/20/devlog-godot-game-working-on.html</guid>
      <description>&lt;p&gt;Time to focus on backdrops. &lt;em&gt;Aaaaargh!&lt;/em&gt; What it&amp;rsquo;s time for is to face my inability to make artwork. I&amp;rsquo;ve been able to do small things: lifts, signs, even some pickups. But for the larger stuff, I don&amp;rsquo;t know where to begin. And regretfully it fills me with shame that I&amp;rsquo;m even looking at asset packs: despite most of the assets in this game &lt;a href=&#34;https://brackeysgames.itch.io/brackeys-platformer-bundle&#34;&gt;already originating from an asset pack&lt;/a&gt;. But the alternative is drawing it myself. And I&amp;rsquo;m not confident that I&amp;rsquo;ll be able to do a good job here.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s try. If it doesn&amp;rsquo;t work, I&amp;rsquo;ll use the asset pack.&lt;/p&gt;
&lt;p&gt;The first idea is to get some inspiration. Well there are plenty of those in my photo collection. Here&amp;rsquo;s one that seems like a good start:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/pxl-20230624-143911534.jpg&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/pxl-20230624-143911534.jpg&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;PXL_20230624_143911534.jpg&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;In short, foothills are slightly jagged yet mainly consist of sweeping curves at roughly the same vertical height. Colour and shadow provide the sense of distance. The hills in the photo are largely covered in vegetation, which could potentially be simulated by a vertical spray-brush: favouring light and dark green with some light brown on a largely mid-level green colour.&lt;/p&gt;
&lt;p&gt;Also I need to remember a principal of illustrations: go from the foreground to the background. I guess one saving grace with modern art tools is that I&amp;rsquo;ve got layers to play around.&lt;/p&gt;
&lt;p&gt;Okay, new sprite. Let&amp;rsquo;s start with the foothills. Taking inspiration from both the photo and the asset pack. Tracing around the foothills and adding some colour to give us depth yields the following:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-18-at-10.23.02.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-18-at-10.23.02.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-18 at 10.23.02.png&#34; 
        
  /&gt;
&lt;/a&gt;


Okay, not bad. A little plain but a good first start.&lt;/p&gt;
&lt;p&gt;Trying out some ridge-lines.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-18-at-10.27.33.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-18-at-10.27.33.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-18 at 10.27.33.png&#34; 
        
  /&gt;
&lt;/a&gt;


Ugh, that looks awful! Not convincing at all. Okay, forget about the ridge-lines. I also tried out adding a spray to simulate vegetation but that didn&amp;rsquo;t look convincing either. I think given the distance, my skill level, and the fact that this is a backdrop, less might be more here.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try it out in the game. I&amp;rsquo;ll use the existing blue sky image I have for the desert world. Let&amp;rsquo;s also remove the existing backdrop.&lt;/p&gt;
&lt;p&gt;Hmm, the issue is trying to get the hills to look convincing, and given that the player starts quite high, and given that the camera is position to track the player near the centre of the screen, they won&amp;rsquo;t see this background layer. But this might be okay as I also need to make a distant mountain layer too.&lt;/p&gt;
&lt;p&gt;Okay, after a lot of trial and error, I&amp;rsquo;ve got the foothills positioned in a way that works well:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.11.36.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.11.36.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-19 at 09.11.36.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;In order to do this I had to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scale them down by 50% so that they don&amp;rsquo;t dominate the screen or have too large pixel edges.&lt;/li&gt;
&lt;li&gt;Use a horizontal scroll offset of 0.7 and a vertical scroll offset of 0.2 so they look convincing in the background. I did try a vertical scroll offset of 0.0 to keep them anchored at the bottom of the screen. But because of how the camera follows the player vertically as they jump, that just looked disorientating.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few things I&amp;rsquo;ve learnt about using &lt;code&gt;Parallax2D&lt;/code&gt;: you want the &amp;ldquo;repeat size&amp;rdquo; to be the size of the sprite (after transforms) so that they&amp;rsquo;re laid out seamlessly across the screen. If the scaled size doesn&amp;rsquo;t make it all away across, bump the &amp;ldquo;repeat times&amp;rdquo; up a few times. You will get the automatic repeat, which you can verify during play through by overriding the camera, zooming all the way out, and turning on 2D controls:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.19.24.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.19.24.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-19 at 09.19.24.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s time for the mountains. For this I&amp;rsquo;m going to use &lt;a href=&#34;https://bearbreath.itch.io/parallax-mountain-background&#34;&gt;this asset pack&lt;/a&gt; for inspiration. Obviously much taller than the foothills, with jagged peaks. Less sweeping curves, and more like upside-down Vs, yet with flat tops.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s attempt one:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.39.32.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.39.32.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-19 at 09.39.32.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;No, don&amp;rsquo;t like it at all. For one thing the canvas is too wide in the vertical direction and too short in the horizontal direction. For another, the mountains don&amp;rsquo;t look convincing. The peaks are too pointy. So what I&amp;rsquo;ll do is cut the canvas in half and try again with a steadier brush.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s attempt two:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.50.03.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-19-at-09.50.03.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-19 at 09.50.03.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;A little better. The general shapes look okay. Might look better after colouring it in.&lt;/p&gt;
&lt;p&gt;Okay, here&amp;rsquo;s the mountain layer with the colouring:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.00.18.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.00.18.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-20 at 21.00.18.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s almost convincing. Let&amp;rsquo;s try it out in the scene.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.25.56.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.25.56.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-20 at 21.25.56.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.26.56.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.26.56.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-20 at 21.26.56.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Hmm, I&amp;rsquo;m not sure I like it. It looks a little cartoon-ie, what with it&amp;rsquo;s large, sweeping areas filled with a single colour. I also don&amp;rsquo;t like the outlines: they&amp;rsquo;re a little too bold. It may help if I made the mountain layer a little smaller and reduce the motion a little.&lt;/p&gt;
&lt;p&gt;Okay, after doing that, I like it a little more. The outlines are less pronounced and you see the more of the mountain plate throughout the level. I added back the outline in the foothill plate. I think you can either go with outlines, or you go without: mixing the two looks bad.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.38.12.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;6dad51e49e4b55baeeaca0ad769758db&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-20-at-21.38.12.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-20 at 21.38.12.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll try this for a while. I may play around with the scroll settings a bit but it is difficult coming up with a combination that will show the mountains for more of the level while also hiding the seams between the layers. I may need to adjust the contrast a little, just so that the background doesn&amp;rsquo;t clash with the foreground: even in the screenshot about it&amp;rsquo;s a little muddy. I also need to put in a cloud layer too. Not quite sure how I&amp;rsquo;ll make convincing looking clouds, but I&amp;rsquo;ll think of something.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/14/finally-added-some-checkpoints-to.html</link>
      <pubDate>Tue, 14 Oct 2025 21:51:47 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/14/finally-added-some-checkpoints-to.html</guid>
      <description>&lt;p&gt;Finally added some checkpoints to level 1-3. Also added some animated water as a new type of hazard. The asset pack I&amp;rsquo;m using came with a single &amp;ldquo;wave&amp;rdquo; tile. I used Aseprite to shift that across 4 cells and configured it as an animation in the tile set. I was planning to add some background waves too, but that made it a little too busy and hard to discern. Less is more here.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-14-at-21.43.35.gif&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;ae947d93f67e658bbd692d98683f393f&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-14-at-21.43.35.gif&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-14 at 21.43.35.gif&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/13/a-small-devlog-entry-today.html</link>
      <pubDate>Mon, 13 Oct 2025 22:02:13 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/13/a-small-devlog-entry-today.html</guid>
      <description>&lt;p&gt;A small devlog entry today. Just painting the level, adding background tiles and kill-zones. I also added a frame around the lift door, just to define it a little. Discovered that it&amp;rsquo;s probably best not to have sprites with non-even dimensions. It makes positioning them a little awkward, resulting in fractional offsets. I elected to include a transparent row at the top of the frame sprite, just to avoid this.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-13-at-21.54.55.png&#34; width=&#34;600&#34; height=&#34;372&#34; alt=&#34;Auto-generated description: A pixel art-style platform game scene features a character standing near a doorway, surrounded by brick walls and yellow platforms, with spikes visible on the right.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - A Trigger That Reveals Secrets</title>
      <link>https://lmika.org/2025/10/12/devlog-godot-game-a-trigger.html</link>
      <pubDate>Sun, 12 Oct 2025 11:09:40 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/12/devlog-godot-game-a-trigger.html</guid>
      <description>&lt;p&gt;Okay, I think I need something to hide secret areas from the player and reveal it to them when they approach it. I&amp;rsquo;ve lived without this mechanic for a while, placing secret areas a fair distance from the main game area, so that they&amp;rsquo;re not visible. But such distances make it difficult to place time-limited things within those secret areas, like the invulnerability power-up, since travelling back to the main area to use it would eat up into the player&amp;rsquo;s power-up time. I could add ways for the player to return to the main play area quickly, like doors, but that complicates the level geometry and I need to place them out of the way, like in the sky.&lt;/p&gt;
&lt;p&gt;So, I&amp;rsquo;ll add a new element to do this. It will be an &lt;code&gt;Area2D&lt;/code&gt; node with an associated tile layer as a child. When the player enters the &lt;code&gt;Area2D&lt;/code&gt;, the tile layer is hidden away and the player can see the revealed secret. The player leaving the &lt;code&gt;Area2D&lt;/code&gt; will make the tile layer visible again.&lt;/p&gt;
&lt;p&gt;Okay, the new &lt;code&gt;SecretReveal&lt;/code&gt; scene has been built. The root node is an &lt;code&gt;Area2D&lt;/code&gt; which takes, as children when added to the main scene, a &lt;code&gt;CollisionShape2D&lt;/code&gt; to act as the trigger space, and a tile layer to act as the tiles to hide/show. The tile layer needs to be in the foreground, which is Z Index 10 and with collision turned off, so that it&amp;rsquo;ll reveal the mid and background layers. The script simply tracks entering bodies (it monitors layer 2, which is dedicated to the player).&lt;/p&gt;
&lt;p&gt;Now, the next question is how to fade the tile map in and out. I could just toggle the visibility but that&amp;rsquo;s way too jarring. What I hope to achieve is to add an animation player to &lt;code&gt;SecretReveal&lt;/code&gt; which will play a fade transition as the player enters or exits the &lt;code&gt;Area2D&lt;/code&gt;. But how to do that without using scripting? I rather use the primitives offered to me as I&amp;rsquo;m guessing they&amp;rsquo;ll be more efficient than throwing events around in GDSscript.&lt;/p&gt;
&lt;p&gt;One possibility is the &lt;code&gt;Modulate&lt;/code&gt; option in the &lt;code&gt;CanvasItem&lt;/code&gt; properties. After quickly playing around with this in the editor, it looks like it&amp;rsquo;s possible to adjust the alpha channel on this property, and that would adjust the visibility of the tile layer:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.02.17.gif&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;057d625dbf85066aaf4426135761271e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.02.17.gif&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-12 at 10.02.17.gif&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;So this looks viable. I&amp;rsquo;m actually glad that &lt;code&gt;Area2D&lt;/code&gt; has this property, despite not actually rendering itself in any way (outside of the editor) Let&amp;rsquo;s give it a try.&lt;/p&gt;
&lt;p&gt;Okay, that works. Hooking it up to an animation player playing an animation that fades out and fades in the tiles when the player enters and leaves the trigger area works. It needs some improvement though. If the player were to enter and leave the target area quickly, the visibility would quickly &amp;ldquo;jump&amp;rdquo; from partially visible to fully visible. I suspect the reason for this is that the animation is always starting from the start.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.10.53.gif&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;057d625dbf85066aaf4426135761271e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.10.53.gif&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-12 at 10.10.53.gif&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;One technique I used in the past is to preserve the current timestamp of the animation when I change it. But the animations here are using non-linear easing as it makes for a better transition.&lt;/p&gt;
&lt;p&gt;So what I&amp;rsquo;ll try is a single animation that will be played forwards and backwards. When there&amp;rsquo;s a need to play the animation, the logic will detect whether the animation player is currently playing, and will resume playback from it&amp;rsquo;s current position in the desired direction.&lt;/p&gt;
&lt;p&gt;Okay, so this is what I settled with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-gdscript&#34; data-lang=&#34;gdscript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; _on_body_entered(body: &lt;span style=&#34;color:#a6e22e&#34;&gt;Node2D&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; void:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	candidate_body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_playing():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;play_section(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;reveal_secret&amp;#34;&lt;/span&gt;, \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		    animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current_animation_position)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;play(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;reveal_secret&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; _on_body_exited(body: &lt;span style=&#34;color:#a6e22e&#34;&gt;Node2D&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; void:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; body &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; candidate_body:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		candidate_body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_playing():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;play_section(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;reveal_secret&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;, \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			    animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current_animation_position, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;, true)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			animation_player&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;play_backwards(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;reveal_secret&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What I&amp;rsquo;m doing is when the player enters the trigger space, I&amp;rsquo;ll play the &amp;ldquo;reveal secret&amp;rdquo; animation, either from the start or from the current position, depending if the animation player is playing. If the player leaves the trigger space, and the animation player is not playing, I&amp;rsquo;ll play the &amp;ldquo;reveal secret&amp;rdquo; in reverse from the end. When it is playing, I do something similar but I need to set the end position to the current animation position, rather than the start position. I&amp;rsquo;m guessing the animation player will take the initial position as the end when it&amp;rsquo;s playing in reverse, which makes sense.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how the transition looks now:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.27.25.gif&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;057d625dbf85066aaf4426135761271e&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-12-at-10.27.25.gif&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-12 at 10.27.25.gif&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Much smoother.&lt;/p&gt;
&lt;p&gt;Now to try it out in level 3-1. And yeah, this is a much better way to do secrets. I can build them close to the play area and the player no longer needs to navigate them blindly, or walk a million miles just to get to the bonus. I won&amp;rsquo;t overdo it though. I&amp;rsquo;m going to keep some of the large secrets that are naturally out of the way as they were. The player will need to react fast to enter them so I want to make sure they have the opportunity to see them.&lt;/p&gt;
&lt;p&gt;I am wondering whether to merge this with the &amp;ldquo;You found a secret&amp;rdquo; trigger, but I think I&amp;rsquo;ll keep them separate for now.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - More on Level 3-1</title>
      <link>https://lmika.org/2025/10/11/devlog-godot-game-more-on.html</link>
      <pubDate>Sat, 11 Oct 2025 21:10:10 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/11/devlog-godot-game-more-on.html</guid>
      <description>&lt;p&gt;Starting on the Godot game again. Level 3-1 is nearing its finish. I&amp;rsquo;m building the corridor/cave that&amp;rsquo;ll lead to the lift that the player will travel up to the exit. To add some interest, I&amp;rsquo;m terminating the corridor at a spike section which should be impossible for the player to navigate. But now I&amp;rsquo;m wondering: what if the player managed to navigate this with invulnerability? Maybe a secret where the player picks up a vile given them this power that would allow them to travel through this section and pick up something nice?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll come back to that. I&amp;rsquo;ll build the lift first and give it a try to see how it feels.&lt;/p&gt;
&lt;p&gt;Need to remember how these lifts work. Okay, yeah, it&amp;rsquo;s all driven by the doors. Added the lift and gave the level a test from the midpoint. Feels good. I do like the lift. I jumped around the spike section a little and yeah I think something like a secret allowing the player to travel through that section for a bonus would be nice.&lt;/p&gt;
&lt;p&gt;First implementation of the invulnerability power-up: simple enough. The current implementation taps into the existing death handler, in which an Area2D trigger will signal to the player to die when they enter a trigger area. Note that this happens when the player &lt;em&gt;enters&lt;/em&gt; the area. If the player is already in the area, nothing happens. This means that if the player enters an area, and stays there when the invulnerability power-up times out, they&amp;rsquo;re still invulnerable until they exit that area. A way around this is to constantly signal to the player to die while they&amp;rsquo;re inside, but I think I&amp;rsquo;ll keep it this way for now. I like the idea of giving the player just a little more time than they have to be invulnerable (although when they leave and enter a new kill zone, they will die).&lt;/p&gt;
&lt;p&gt;Okay, implemented the invulnerability power-up and gave it a quick test in the spiky area. Yeah, it works. I haven&amp;rsquo;t added the kill zones yet so the need for invulnerability is completely unnecessary, but the progress bar has been implemented, and based on how quickly that runs down, there&amp;rsquo;s ample time for the player to get the pickups and get back before the bar runs out.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-11-at-10.17.02.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;86fd78ccac1ad324ea845862fdf12816&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-11-at-10.17.02.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-11 at 10.17.02.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Place the exit and now just playing through the level. It needs checkpoints, and there are some tricky jumps that I&amp;rsquo;m tweaking. But I think it plays rather well. I&amp;rsquo;ll start dressing up the level now.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/10/10/some-advice-for-the-tldraw.html</link>
      <pubDate>Fri, 10 Oct 2025 09:17:26 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/10/some-advice-for-the-tldraw.html</guid>
      <description>&lt;p&gt;Some advice for the TLDraw Obsidian plugin maintainer: drop everything after the words &amp;ldquo;and&amp;rdquo; and &amp;ldquo;then&amp;rdquo; in these menu items, then remove duplicates. This should bring the menu items down to a number that&amp;rsquo;s easy to scan. I can manipulate tabs myself, but finding what I want here is a lot.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20251010-091302.png&#34; alt=&#34;Auto-generated description: A dropdown menu lists various options for creating or importing drawings in tldraw.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - More On Level 3-1</title>
      <link>https://lmika.org/2025/10/09/devlog-godot-game-more-on.html</link>
      <pubDate>Thu, 09 Oct 2025 22:19:54 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/09/devlog-godot-game-more-on.html</guid>
      <description>&lt;p&gt;Working on the Godot game again. Finishing off the top layer of zone 3 in 3-1. Made a floating balloon with a smaller drop-payload trigger area, just to avoid accidental triggers if the player gets too close.&lt;/p&gt;
&lt;p&gt;One thing I didn&amp;rsquo;t appreciate is that if the player misses a jump, it takes a long time for them to fall and recover from that. I&amp;rsquo;m wondering if I need to add some kill zones between each horizontal level of the cross-back, along some checkpoints so that the player doesn&amp;rsquo;t have to spend so much time waiting to fall and redoing large chunks of the zone.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll try this will some barbed-wire graphics. See how I feel about it. Here&amp;rsquo;s how they looks:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-09-at-21.45.49.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;487a0b931c939eca484b5e4434df7187&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-09-at-21.45.49.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-09 at 21.45.49.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Doesn&amp;rsquo;t look inviting, although that&amp;rsquo;s kind of the point. But this may work as a kill-zone between hight levels. Although I&amp;rsquo;m uncertain as to how I&amp;rsquo;m going to introduce them to the player. Maybe they won&amp;rsquo;t need any introduction: some ugly, spiky coils might be deterrent enough.&lt;/p&gt;
&lt;p&gt;Anyway, the elements of the actual jumping part has been filled in. I think adding this was a good idea: feels interesting again. Now it&amp;rsquo;s just a matter of adding the final lift, and then the exit.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game - More On Level 3-1</title>
      <link>https://lmika.org/2025/10/08/devlog-godot-game-more-on.html</link>
      <pubDate>Wed, 08 Oct 2025 22:07:17 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/08/devlog-godot-game-more-on.html</guid>
      <description>&lt;p&gt;Continuing the build out of the zone 3 in level 3-1. I&amp;rsquo;ll start with a platform that is self rising when the player jumps on it. Always a classic; and honestly, I think the start of the level can use that. Lift does feel a little slow. I boosted the speed from 70 to 90. Also started added floating balloons with mines around this part of the level, since those elements were introduced in zone 2.&lt;/p&gt;
&lt;p&gt;Hmm, I wonder if I should make a variant of the red balloon with mine. The mines will kill you, but the player can safely land on the top of the red balloon. I&amp;rsquo;m wondering if a variant that is unsafe for the player could be useful. Maybe a  &amp;ldquo;spiky balloon&amp;rdquo; type. It can be attached to the grey mine I made for level 2-2.&lt;/p&gt;
&lt;p&gt;Alternatively, I can make it a different colour and just make it such that landing on it will pop the balloon. Took a brief look at a colour blindness test and it looks like red and green is the way to go for two distinguished colours.&lt;/p&gt;
&lt;p&gt;Hit a roadblock: I need to import a type, the &lt;code&gt;TextureVariant&lt;/code&gt; enum, from one script (the static mine) into another one (balloon_with_static_mine). &lt;a href=&#34;https://forum.godotengine.org/t/how-do-i-include-a-script-into-another-script/19217&#34;&gt;Quick web search&lt;/a&gt; and it looks like the way to do this is by &amp;ldquo;preloading&amp;rdquo; a script. This gives you access to the script&amp;rsquo;s symbols:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-gdscript&#34; data-lang=&#34;gdscript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; StaticMine &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; preload(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;res://scripts/static_mine.gd&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; mine_texture_variant: StaticMine&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;TextureVariant
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To support popping when the player lands on it, the balloon is an &lt;code&gt;AnimatableBody2D&lt;/code&gt; which can detect collisions as it bobs up and down. I wonder if I can actually test collision that way:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-gdscript&#34; data-lang=&#34;gdscript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; coll &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; animatable_body_2d&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;move_and_collide(velocity)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; coll &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; null:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; coll&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_collider()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Player&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Bang!&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No, turns out I can&amp;rsquo;t. Tried this and even played around with layers a little, and it didn&amp;rsquo;t detect when the player landed on it. So moving to plan B, which is adding an Area2D set to layer 2, the same layer as the player. When the player lands in that, it will pop the balloon… eventually. At the moment it just makes the balloon disappear. I&amp;rsquo;ll need to add the pop animation and sound effects. It will also give the player a jump at at about the same distance they would travel in a normal jump.&lt;/p&gt;
&lt;p&gt;Okay, new variant implemented. Here&amp;rsquo;s how it looks:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-08-at-21.51.39.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;0d8f284ff5fe12ea5c49f3cc0c6878b6&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-08-at-21.51.39.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-08 at 21.51.39.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;I was going for something a little deadlier, but I think having the extra jump could make for some interesting mechanics. I guess we&amp;rsquo;ll see. Anyway, I&amp;rsquo;ll leave it there this evening. I&amp;rsquo;ve managed to get 2/3rds of new section built out.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools - Podcast Clip Favourites</title>
      <link>https://lmika.org/2025/10/06/devlog-blogging-tools-podcast-clip.html</link>
      <pubDate>Mon, 06 Oct 2025 22:29:36 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/06/devlog-blogging-tools-podcast-clip.html</guid>
      <description>&lt;p&gt;Back on Blogging Tools. Gave up on trying to set a remote ref: all the ones I&amp;rsquo;ve tried were not working. Looking at the examples it looks like I can call &lt;code&gt;Push&lt;/code&gt; with defaults (apart from authentication which looks like I&amp;rsquo;ll require with my repo). Gave it a try and that looks like it worked. I peeked in the source code and the default ref that work was the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;DefaultPushRefSpec&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;refs/heads/*:refs/heads/*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns out by adding &lt;code&gt;refs/remote/origin/…&lt;/code&gt; I was using the default pull ref. I suppose I should learn more about how Git works in this respect. But later, it&amp;rsquo;s working now. That&amp;rsquo;s all I need. Finishing off the Blogging Tool side of things by moving all the hard coded values to the config, and I think this is ready to go.&lt;/p&gt;
&lt;p&gt;Turning to the Hugo site. I&amp;rsquo;ve started a new site from scratch, which does little than just push to Netlify. Same template and same changes I&amp;rsquo;ve made for the test site. I don&amp;rsquo;t love the template, but I think it&amp;rsquo;ll work for now.&lt;/p&gt;
&lt;p&gt;Created a new site and installed the theme. Set the menu and cleared the footer as per this Hugo config:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;baseURL&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://example.org/&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;languageCode&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;en-us&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;title&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Podcast Favourites&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;theme&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hugo-flex&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;footer&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&lt;span style=&#34;color:#a6e22e&#34;&gt;menu&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Clips&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pageRef&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/about&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;weight&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;markup&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;goldmark&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;renderer&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;unsafe&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Produces this nice clean home page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-06-21.25.37-localhost-48f16a06173b.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;cd2795880879f7ce0b88795de840a206&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-06-21.25.37-localhost-48f16a06173b.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-06 21.25.37 localhost 48f16a06173b.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Added CI/CD to build the site and deploy it to Netlify. All seems to work.&lt;/p&gt;
&lt;p&gt;Merge changes in Blogging Tools, pushed to Forgjo, and redeployed in Coolify. Status is degraded. Reason is that the temp directory, which is where the workspace for the Hugo site&amp;rsquo;s Git repo, doesn&amp;rsquo;t exist:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-06-at-21.48.17.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;cd2795880879f7ce0b88795de840a206&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-06-at-21.48.17.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-06 at 21.48.17.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;I forget that this is deployed in a &lt;code&gt;scratch&lt;/code&gt; container, which has no guest OS. So creating the &lt;code&gt;/tmp&lt;/code&gt; directory is something I&amp;rsquo;ll need to do. Will add a quick bit of logic to Blogging Tools to do this prior to creating the Git provider.&lt;/p&gt;
&lt;p&gt;Actually, no. Change of pace. I&amp;rsquo;ll do it in the Docker file. What I&amp;rsquo;ll do is touch a file in &lt;code&gt;/tmp&lt;/code&gt; during the build phase and just copy it over to scratch (I can&amp;rsquo;t do this in scratch as it doesn&amp;rsquo;t come with executables). This should force the directory creation.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; touch /tmp/t&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ---------  &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; scratch  &lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; --from&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;builder /tmp/t /tmp&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Redeployed and… what?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-06-at-21.58.54.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;cd2795880879f7ce0b88795de840a206&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-06-at-21.58.54.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;CleanShot 2025-10-06 at 21.58.54.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Oh, that&amp;rsquo;s meant to be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; --from&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;builder /tmp/t /tmp/t&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copying directly to &lt;code&gt;/tmp&lt;/code&gt; makes a new file with the filename &lt;code&gt;tmp&lt;/code&gt; in &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Okay, Blogging Tools is working again. Let&amp;rsquo;s try it out.&lt;/p&gt;
&lt;p&gt;Ah, error: &lt;code&gt;author field is required&lt;/code&gt;. I need to set this for the commit call. That may be why the commit author was me and not the &lt;code&gt;bloggingtools&lt;/code&gt; user when I was testing this on my own machine.&lt;/p&gt;
&lt;p&gt;Trying again. Hey, success! Blogging Tools added the clip to the repository and pushed the changes as the &lt;code&gt;bloggingtools&lt;/code&gt; user. This kicked off the CI/CD to rebuild the Hugo site and pushes it to Netlify. Did forget to fix the base URL add the Hugo template for making the audio playable, but fixed that and now the clip is playable.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251006222338.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;cd2795880879f7ce0b88795de840a206&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/pasted-image-20251006222338.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;Pasted image 20251006222338.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Nice! Calling this feature done. I will need to style the Hugo site, but I&amp;rsquo;ll do that over time. It&amp;rsquo;s getting a bit late and I want to wrap this up.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools - Podcast Clip Favourites</title>
      <link>https://lmika.org/2025/10/05/devlog-blogging-tools-podcast-clip.html</link>
      <pubDate>Sun, 05 Oct 2025 17:52:30 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/10/05/devlog-blogging-tools-podcast-clip.html</guid>
      <description>&lt;p&gt;Inspired by the way &lt;a href=&#34;https://ronjeffries.com/articles/-w025/y/t/&#34;&gt;Ron Jeffries writes&lt;/a&gt; about his work, as well as by &lt;a href=&#34;https://blog.martin-haehnel.de/2025/10/03/whiledo/&#34;&gt;Martin Hähnel&lt;/a&gt; attempt at this, I thought for this instalment of Devlog I&amp;rsquo;ll try a more &amp;ldquo;lab notes&amp;rdquo; approach. This means a potentially more mundane and less satisfactory description of project work today: less showing of what was accomplished, and more of a running commentary. I wanted to see if I liked this style of writing, and if it helped me or slowed me down. Preliminary results were mixed: it did slow me down, but I found it enjoyable. I&amp;rsquo;ll try this a few more times to see how I feel about it.&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s coding session is to continue the work of adding saved favourite podcast clips from Blogging Tools. Previously this was a standalone application connected to a database, but along with the tech stack used to build this falling out of active support, along with the feeds no longer working for reasons I rather not look into, I want to replace this with a static site that takes nothing to maintain. One other thing is that I want to move away from referencing links to the podcast clips and saving actual audio. Since I&amp;rsquo;m dealing with copyrighted material, some of it that is for pay, this site will be kept private.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m choosing to use Blogging Tools for this as it has a relatively decent podcast clipper that I use for clips I publish to my site. For the static site itself, I&amp;rsquo;m choosing to look at Hugo, given that I&amp;rsquo;m comfortable with this approach too. Although I do have some concerns about the long term viability of this for sites I don&amp;rsquo;t touch that often. An initial attempt at this used another site I use to track audio that used a template that fell out of maintenance and broke when I moved to Hugo 0.151. The Hugo maintainers really need to clean up their versioning act.&lt;/p&gt;
&lt;p&gt;Anyway, the current status of this is a temporary Hugo site with a new template. At this stage, I&amp;rsquo;ve got Blogging Tools running a new job type that will take the clipped audio and image thumbnail, save them as static data to Hugo, and publish a new post.&lt;/p&gt;
&lt;p&gt;At the moment, the template isn&amp;rsquo;t including the thumbnail or audio HTML. This is now a point of committing to this site and template: moving it out of the temp directory and into a proper workspace. So let&amp;rsquo;s start that now. I&amp;rsquo;m using the &lt;a href=&#34;https://github.com/ldeso/hugo-flex&#34;&gt;Hugo Flex&lt;/a&gt; theme as it provides a nice clean canvas to start with, and it seems to be in active maintenance (something to watch out for when dealing with Hugo themes). Added the git module, and setting Goldmark unsafe to true (once again) and now the new Hugo site is ready:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.35.03-localhost-fd2731f7bcc3.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.35.03-localhost-fd2731f7bcc3.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.35.03 localhost fd2731f7bcc3.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now testing this with Blogging Tools. I&amp;rsquo;ve already got an ATP clip ready to go. And after remembering to change the target directory from the temp Hugo site to the real one, I am ready to test the extract. All that involves is clicking &amp;ldquo;Save&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.36.41-localhost-bdf14f391c73.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.36.41-localhost-bdf14f391c73.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.36.41 localhost bdf14f391c73.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Okay, that didn&amp;rsquo;t work for a very stupid reason. I set the target directory to Blogging Tools repo, not the Hugo site. Made the change (Blogging Tools is being run using &lt;a href=&#34;https://github.com/air-verse/air&#34;&gt;Air&lt;/a&gt; which gives me file watching capabilities one would see in the frontend world) and can now see that the new post was written to the new Hugo site.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.43.17-localhost-42cb85cfbe26.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.43.17-localhost-42cb85cfbe26.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.43.17 localhost 42cb85cfbe26.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Now to adjust the template a little, just to add the audio and thumbnail. The Markdown files produced by Blogging Tools have all the clip information in the front matter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;title: &amp;#34;449:  An Unclean Mouse&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;episode:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  guid: some-random-id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  url: &amp;#34;https://atp.fm/449&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  title: &amp;#34;449:  An Unclean Mouse&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  date: 2021-09-23 12:27:49 +1000 AEST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  image_src: &amp;#34;https://cdn.atp.fm/artwork-bootleg&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  audio_src: &amp;#34;https://example.com/file.mp3&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;show:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  title: &amp;#34;Accidental Tech Podcast: Unedited Live Stream&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;clip:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  start: 0s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  duration: 60ms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;date: 2021-09-23 12:27:49 +1000 AEST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;thumbnail: images/DQkt_Lk5QRjXVHE0rdR09.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;audio: audio/5fFX8m0BqqFqTwlVAtb2Y.mp3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Test thing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, before I do that. Let me fix the dates should that they&amp;rsquo;re in UTC and formatted as ISO 8601. This is a quick change to the template used by Blogging Tools:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;date: {{.Clip.EpisodeDate.UTC.Format &amp;#34;2006-01-02T15:04:05Z07:00&amp;#34;}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another quick test… perfect. The dates are coming through again. Oh, and a quick aside: because the front matter is all included in one Go template, I had to make sure to properly quote the string values, lest they contain colons and other constructs YAML doesn&amp;rsquo;t like. Easy way to do that is pipe them through &lt;code&gt;printf &amp;quot;%q&amp;quot;&lt;/code&gt;, which quotes them as if they&amp;rsquo;re Go strings. May not be perfect but it&amp;rsquo;s good enough for now:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;title: {{.Clip.EpisodeTitle | printf &amp;#34;%q&amp;#34;}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, lets fix the template. I&amp;rsquo;ll start by creating a new template in &lt;code&gt;layouts/clips/singles.html&lt;/code&gt; which is a copy-and-paste of the existing theme post. I&amp;rsquo;ll then add details of the front matter, such as the thumbnail and audio player. This I can get using the &lt;a href=&#34;https://gohugo.io/configuration/params/&#34;&gt;Params&lt;/a&gt; template construct. I&amp;rsquo;ll start by just printing it:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-gotemplate&#34; data-lang=&#34;gotemplate&#34;&gt;{{ define &amp;#34;main&amp;#34; }}

  {{ $context := dict &amp;#34;page&amp;#34; . &amp;#34;level&amp;#34; 1 &amp;#34;isdateshown&amp;#34; true }}
  {{ partial &amp;#34;heading.html&amp;#34; $context }}
  
  Audio = {{.Params.audio}}, image = {{.Params.thumbnail}}
  
  {{ .Content }}
  {{ partial &amp;#34;tags.html&amp;#34; . }}
  {{ partial &amp;#34;comments.html&amp;#34; . }}

{{ end }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.54.52-localhost-c59d83c214d9-1.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.54.52-localhost-c59d83c214d9-1.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.54.52 localhost c59d83c214d9 1.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;I had to restart Hugo, but that&amp;rsquo;s coming through. I need to convert these into proper URLs though. I&amp;rsquo;ll start by trying the &lt;a href=&#34;https://gohugo.io/functions/urls/absurl/&#34;&gt;absurl&lt;/a&gt; template function:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-gotemplate&#34; data-lang=&#34;gotemplate&#34;&gt;Audio = {{ absURL .Params.audio}}, image = {{ absURL .Params.thumbnail}}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.57.47-localhost-392aecb7bb5f-1.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.57.47-localhost-392aecb7bb5f-1.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.57.47 localhost 392aecb7bb5f 1.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Okay, getting better. Let&amp;rsquo;s try and turn them into proper HTML elements:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  &amp;lt;img src=&amp;#34;{{ absURL .Params.thumbnail}}&amp;#34;&amp;gt;
  &amp;lt;audio src=&amp;#34;{{ absURL .Params.audio}}&amp;#34; controls&amp;gt;&amp;lt;/audio&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.59.35-localhost-56693aeddfc3-1.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-09.59.35-localhost-56693aeddfc3-1.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 09.59.35 localhost 56693aeddfc3 1.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Good, the image and audio is coming through. Image looks a little large but I&amp;rsquo;ll fix that at little later. Oh, and it looks like the clips are showing up in the home page already. That&amp;rsquo;s a nice touch. I do want to change the clip paths though. At the moment, they&amp;rsquo;re saved using the auto-incrementing clip ID, which is not ideal. Clip metadata is stored in an Sqlite database and the library I&amp;rsquo;m using — which is &lt;a href=&#34;modernc.org/sqlite&#34;&gt;a Go port of Sqlite3 that&lt;/a&gt; doesn&amp;rsquo;t need CGO — has a nasty habit of reusing row IDs. I really should replace this with an auto-generated primary key. But that&amp;rsquo;s for later.&lt;/p&gt;
&lt;p&gt;Right now, I think I&amp;rsquo;ll use a hash of the episode Guid for the path. ATP seems to have randomly generated hashes but I know that some RSS feeds choose to use URLs, which won&amp;rsquo;t do for a path. Why the Guid? Well, I want to be able to regenerate a clip if need be, replacing the existing one.&lt;/p&gt;
&lt;p&gt;Actually, no, that won&amp;rsquo;t do. I want to be able to create multiple clips from the same episode. So maybe I&amp;rsquo;ll use a hash that includes the Guid and clip timestamp. So let&amp;rsquo;s try that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;clipPathRaw&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprintf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%v:%v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;clip&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EpisodeGUID&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;clip&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ClipStart&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;clipPathHash&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;md5&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sum&lt;/span&gt;([]byte(&lt;span style=&#34;color:#a6e22e&#34;&gt;clipPathRaw&lt;/span&gt;))  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;clipPath&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hex&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;EncodeToString&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;clipPathHash&lt;/span&gt;[:])  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;postFilename&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;filepath&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Join&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;postDir&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprintf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%v.md&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;clipPath&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MD5 should be fine: these don&amp;rsquo;t need to be cryptographically safe. Also, &lt;code&gt;md5.Sum&lt;/code&gt; returns an &lt;code&gt;[16]byte&lt;/code&gt; array, and I keep forgetting how to return this as a &lt;code&gt;[]byte&lt;/code&gt; slice. Apparently it&amp;rsquo;s &lt;code&gt;array[:]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another quick test. Yeah, it works. The clip paths now look like this: &lt;code&gt;http://localhost:1313/clips/210da5b665c38c809eb1ea481b1b22de/&lt;/code&gt; Not great, but I tend to browse this visually so I think I can live with this.&lt;/p&gt;
&lt;p&gt;Okay, next thing is having Blogging Tools generate the clips and commit them to Git. The goal is to have the Hugo site stored in a Git repository and built to a private, undisclosed website. I did this for Nano Journal, which synchronised journal entries to a Git repo after saving it locally. Looking at the code, though, I have no idea how I setup the credentials. There&amp;rsquo;s nothing in the config hinting at a certificate so I may have done it manually.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also wondering if I can use &lt;a href=&#34;https://github.com/go-git/go-git&#34;&gt;Go Git&lt;/a&gt; for this. I didn&amp;rsquo;t use this for Nano Journal, as I wanted to use LFS; which Go Git doesn&amp;rsquo;t support (instead I just shelled out to &lt;code&gt;git&lt;/code&gt;). But I&amp;rsquo;m wondering if I can live without LFS for this.&lt;/p&gt;
&lt;p&gt;Okay, let&amp;rsquo;s try this. I&amp;rsquo;ll push the current Hugo site to a test repo on my Forgejo instance and see whether I can check it out from Blogging Tools. This will be done as a user that I&amp;rsquo;ll configure Blogging Tools to act as.&lt;/p&gt;
&lt;p&gt;Then I added Go Git to blogging tool and created a new provider for Git. Added a method to either pull or clone a remote repository, checkout a branch, and pull updates from the repo. I&amp;rsquo;ve chosen to use the file system for keeping the local workspace so that Blogging Tools doesn&amp;rsquo;t need to keep cloning the repo whenever there&amp;rsquo;s a need to add a clip. This I&amp;rsquo;m just going to keep in ephemeral storage so that when the Docker container gets cycled and the workspace lost, it&amp;rsquo;ll just recreate it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Provider&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;CloneOrPull&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; (  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Repository&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;isDir&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;workspace&lt;/span&gt;) {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;// Workspace doesn&amp;#39;t exist. Pull from repo.  
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PlainClone&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;workspace&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CloneOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;URL&lt;/span&gt;:               &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repoURL&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;RecurseSubmodules&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DefaultSubmoduleRecursionDepth&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;Auth&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;BasicAuth&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             &lt;span style=&#34;color:#a6e22e&#34;&gt;Username&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;             &lt;span style=&#34;color:#a6e22e&#34;&gt;Password&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       })  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PlainOpen&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;workspace&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;worktree&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Worktree&lt;/span&gt;()  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;worktree&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pull&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PullOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;RemoteURL&lt;/span&gt;:         &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repoURL&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;RecurseSubmodules&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DefaultSubmoduleRecursionDepth&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;ReferenceName&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;plumbing&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ReferenceName&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;branch&lt;/span&gt;),  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Auth&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;BasicAuth&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;Username&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;Password&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;worktree&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Checkout&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CheckoutOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Branch&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;plumbing&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ReferenceName&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;branch&lt;/span&gt;),  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quick test of this, and I&amp;rsquo;m seeing a &amp;ldquo;repository doesn&amp;rsquo;t exist&amp;rdquo; error.&lt;/p&gt;
&lt;p&gt;Actually, no. That was because of an earlier use of the repository URL when I actually wanted the workspace. But now I&amp;rsquo;m seeing a &amp;ldquo;reference not found&amp;rdquo; error when I try to pull from the origin:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-11.24.51-localhost-87f4f1d9b693.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-11.24.51-localhost-87f4f1d9b693.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 11.24.51 localhost 87f4f1d9b693.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Ah, okay. I should&amp;rsquo;ve used &lt;code&gt;plumbing.NewBranchReferenceName&lt;/code&gt; here. That&amp;rsquo;s fix that. Now it&amp;rsquo;s an &lt;code&gt;already up-to-date&lt;/code&gt; error. That&amp;rsquo;s fine, I&amp;rsquo;ll just add an ignore case here:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;worktree&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pull&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PullOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RemoteURL&lt;/span&gt;:         &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repoURL&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RecurseSubmodules&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DefaultSubmoduleRecursionDepth&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ReferenceName&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;plumbing&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewBranchReferenceName&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;branch&lt;/span&gt;),  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Auth&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;BasicAuth&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Username&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Password&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; !&lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NoErrAlreadyUpToDate&lt;/span&gt;) {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Another test:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-11.28.44-localhost-055ec2134222.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/2025-10-05-11.28.44-localhost-055ec2134222.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;2025-10-05 11.28.44 localhost 055ec2134222.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;And that worked. I managed to pull changes from remote.&lt;/p&gt;
&lt;p&gt;Deleting the workspace and trying a clone. Hmm, that&amp;rsquo;s strange. I&amp;rsquo;m seeing an &lt;code&gt;authentication required&lt;/code&gt; error, yet it looks like the repository was cloned successfully. Let me try and ignore that error.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;warn: error received alongside repo: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, that worked. It&amp;rsquo;s not pretty, but I think it&amp;rsquo;s fine for now.&lt;/p&gt;
&lt;p&gt;Now to commit the new files to the repository. Although partial failures from staging multiple files — one file is stage but the second one fails for some reason — may introduce some problems where the state is not completely settled, so I may also need a method of ensuring the workspace is pristine: no files should exist after cloning or pulling that is not staged. So need to add that first. I don&amp;rsquo;t see a method to do this on the Workspace type, so basically what I&amp;rsquo;ll do is reset hard, and then iterate over the untracked files and remove them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Reset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResetOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Mode&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HardReset&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Status&lt;/span&gt;()  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Worktree&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Untracked&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;os&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Remove&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, that&amp;rsquo;s the plan eventually. I&amp;rsquo;ll just log them for now.&lt;/p&gt;
&lt;p&gt;Then it&amp;rsquo;s just a matter of adding files and commit the changes. First attempt at this failed with an &lt;code&gt;entry not found&lt;/code&gt; error. That was just a bad path. Adding files now working, along with committing, and now we&amp;rsquo;re failing at the push. Reason is that unlike all the other Go Git methods, which were pretty high level, this one leaks a bit of the mechanics of Git, requiring the use of a &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Internals-The-Refspec&#34;&gt;Ref Spec&lt;/a&gt;. I&amp;rsquo;ve just hard coded it to a version which will push the local main branch to the origin main branch.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Push&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PushOptions&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RemoteURL&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;repoURL&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Auth&lt;/span&gt;: &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;BasicAuth&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Username&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;Password&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;password&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RefSpecs&lt;/span&gt;: []&lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RefSpec&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RefSpec&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprintf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+refs/heads/%v:refs/remotes/origin/%v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;branch&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;branch&lt;/span&gt;)),  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, before that, I finding a &lt;code&gt;cannot create empty commit: clean working tree&lt;/code&gt;. I&amp;rsquo;m opening the work tree between staging the files and committing. Is that the problem?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-05-at-21.57.36.png&#34; 
   class=&#34;glightbox&#34;
   data-gallery=&#34;9ea5d8956f0e4d3138d476569919f9c7&#34;
   
&gt;
  &lt;img src =&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-10-05-at-21.57.36.png&#34; 
       loading=&#34;lazy&#34;
       decoding=&#34;async&#34;
       style=&#34;border-radius: 5px; max-width: 100%&#34;
       alt=&#34;cleanshot-2025-10-05-at-21.57.36.png&#34; 
        
  /&gt;
&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;Hmm, no. It might be that I&amp;rsquo;m adding the full path of the file to the index. Running &lt;code&gt;git status&lt;/code&gt; in that repository yielded full path entries, which is very uncharacteristic. Furthermore, trying to unstage them did nothing. I&amp;rsquo;ll remove the repo directory and change the logic to get the relative path of the file before they&amp;rsquo;re added:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;files&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;relPath&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;filepath&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Rel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;workspace&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;relPath&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Commit&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;msg&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CommitOptions&lt;/span&gt;{}); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fault&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, it was because the files I was trying to add were already added to the repository from earlier in the day, and they weren&amp;rsquo;t being changed by the job. Let me remove them from the repository and try again.&lt;/p&gt;
&lt;p&gt;Okay, it was that. Commits are now working, although pushes don&amp;rsquo;t seem to be synchronising the commits. When I look at recent changes in Forgejo, it only shows my commits, not the ones created by the service user I made for Blogging Tools. Will try adjusting the refspec to push all heads:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;RefSpecs&lt;/span&gt;: []&lt;span style=&#34;color:#a6e22e&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;RefSpec&lt;/span&gt;{  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+refs/heads/*:refs/remotes/origin/*&amp;#34;&lt;/span&gt;,  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nah, that didn&amp;rsquo;t help. The method is returning no error, yet it doesn&amp;rsquo;t seem to be synchronising the branches. Will need to learn more about how this all works. But I think that&amp;rsquo;s something for later.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/28/small-change-to-the-thirst.html</link>
      <pubDate>Sun, 28 Sep 2025 12:09:06 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/28/small-change-to-the-thirst.html</guid>
      <description>&lt;p&gt;Small change to the thirst mechanic for my Godot project. Switched from discrete thirst levels to a single timer that will tick down if the player is thirsty. This allowed for a change to how I indicate this to the player, replacing text messages that&amp;rsquo;ll be displayed at each thirst level with a gauge that shows up on the HUD.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250928-105703.png&#34; width=&#34;600&#34; height=&#34;381&#34; alt=&#34;Auto-generated description: A retro-style video game features a knight-like character navigating a platform with a floating pink enemy and a water obstacle below.&#34;&gt;
&lt;p&gt;I thought that the messages would be enough, but after playing through with them, they turned out to be more of a hindrance. They didn&amp;rsquo;t communicate the player&amp;rsquo;s thirst level well enough: they show up for a few seconds, then disappear, leaving the player to wonder how much time they have. This information is now always present with the gauge. Plus, I think it gives more of a sense of urgency, in that a gauge that&amp;rsquo;s constantly ticking down will encourage the player to play a little faster in order to reach the next water bottle before they perish from thirst.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/27/more-on-that-godot-project.html</link>
      <pubDate>Sat, 27 Sep 2025 12:02:07 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/27/more-on-that-godot-project.html</guid>
      <description>&lt;p&gt;More on that Godot project today. Finally bit the bullet and started working on proper backdrops. I&amp;rsquo;ve leaving the artwork until later (i.e. I&amp;rsquo;m procrastinating), today was all about the mechanics, trying to get parallax scrolling working. Fortunately Godot has &lt;a href=&#34;https://docs.godotengine.org/en/stable/tutorials/2d/2d_parallax.html&#34;&gt;built-in nodes to make this easy&lt;/a&gt;, although I did have to upgrade Godot to 4.5.&lt;/p&gt;
&lt;p&gt;This is my first cut, some simple colour banding to represent sky and sand in one of the desert levels:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250927-105652.png&#34; width=&#34;600&#34; height=&#34;381&#34; alt=&#34;Auto-generated description: A pixel-art game interface shows a character in armour on a blocky terrain with a blue sky background and floating coins.&#34;&gt;
&lt;p&gt;These two layers were added to the scene as &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_parallax2d.html#class-parallax2d&#34;&gt;Parallax2D&lt;/a&gt; nodes, each with a single Sprite child. Positioning the sprites was a little tricky. Godot suggests placing them at the origin, but what I didn&amp;rsquo;t understand was that the actual position of the sprite depends a lot on the scroll scale of the Parallax2D node. I added the sky layer without changing the scroll position and it took me a while before I discovered that it was appearing below the camera. Only after reducing the scroll scale to about 0.1 did it start showing up in the viewport (one nice thing about Godot 4.5 is that the &amp;ldquo;play scene&amp;rdquo; window includes debug options, allowing you to hijack the camera and move it around the scene).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250927-105631.png&#34; width=&#34;600&#34; height=&#34;318&#34; alt=&#34;Auto-generated description: A game development interface is displayed, featuring a pixelated game scene with water and blocks being edited in an application, likely Godot Engine.&#34;&gt;
&lt;p&gt;I will need to introduce some detail in the backdrop layers soon. The bands are too simple and may induce some vertigo when the player is ascending to high platforms. Plus, it just looks boring. Not that they&amp;rsquo;re meant to be eye-catching but something a little more interesting would be nice.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/27/just-a-reminder-that-macos.html</link>
      <pubDate>Sat, 27 Sep 2025 10:41:49 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/27/just-a-reminder-that-macos.html</guid>
      <description>&lt;p&gt;Just a reminder that MacOS comes with a grapher, and a pretty decent one at that. Came in handy when I wanted to visualise a few functions.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250927-094034.png&#34; width=&#34;600&#34; height=&#34;411&#34; alt=&#34;Auto-generated description: A graphing software interface displays the function (y = cos(x^2) + 0.5) plotted on a Cartesian coordinate system.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Trying OpenAI Codex to Produce Freelens Logo Creator</title>
      <link>https://lmika.org/2025/09/13/devlog-trying-openai-codex-to.html</link>
      <pubDate>Sat, 13 Sep 2025 10:05:30 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/13/devlog-trying-openai-codex-to.html</guid>
      <description>&lt;p&gt;Tried a bit of vibe coding using OpenAI Codex yesterday.&lt;/p&gt;
&lt;p&gt;I needed something to produce nice logos for &lt;a href=&#34;https://freelensapp.github.io&#34;&gt;Freelens&lt;/a&gt; so I can tell each cluster apart. The default avatar for Freelens is an abbreviation of the cluster name set atop a random background colour, which doesn&amp;rsquo;t do me a lot of good as the cluster names I work with are all similar, and would produce the same abbreviations. Furthermore, Freelens doesn&amp;rsquo;t have a way to change these default avatars. But it does have a way to upload new avatars as an image.&lt;/p&gt;
&lt;p&gt;This was a perfect candidate for trying out OpenAI Codex. A use-case that I needed but was not important nor interesting enough for me to build manually. I mean, knowing me, I probably would&amp;rsquo;ve built it, but it wouldn&amp;rsquo;t have been good use of my time. So I created a new Github repo, connected it to Codex, and gave it this prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would like a static web-page, consisting of one HTML file, and one JavaScript file. This is to be vanilla JavaScript that does not require NPM or any external dependencies.&lt;/p&gt;
&lt;p&gt;This page is to have a form which accepts the following input:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A text input with the name &amp;ldquo;Upper&amp;rdquo; which accepts a string of any kind.&lt;/li&gt;
&lt;li&gt;A text input with the name &amp;ldquo;Lower&amp;rdquo; which accepts a string of any kind.&lt;/li&gt;
&lt;li&gt;A selection for background colour. Instead of a colour picker, please allow the user to select one of 8 distinct colour choices. Each one should be distinct enough such that the user is able to distinguish them from each other. They should be of medium darkness, such that if white text were to be display atop of it, the text should be legible; yet not too dark as to be hard to distinguish.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The page is also to have a canvas element, of about 80 x 80 points, which will take input of the form above and do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fill the background with the chosen background colour.&lt;/li&gt;
&lt;li&gt;Draw a thin dividing line — no greater than 6 px wide — in white across the width of the canvas.&lt;/li&gt;
&lt;li&gt;Print the form value of &amp;ldquo;Upper&amp;rdquo; in uppercase, in a san serif font, centred and above the dividing line in white. The printed text should be large enough to fill most of&lt;/li&gt;
&lt;li&gt;Print the form value of &amp;ldquo;Lower&amp;rdquo; in uppercase, in a san serif font, centred and below the dividing line in white.&lt;/li&gt;
&lt;li&gt;Ensure that a suitable margin of 15 pixels padding around the border of the canvas content, and that the printed text and dividing line is not drawn beyond the margin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The canvas element is to be updated when the form values are changed.&lt;/p&gt;
&lt;p&gt;Below the canvas should be a button which should allow the user to download the canvas image as a PNG.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And here&amp;rsquo;s what it produced:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250913-090254.png&#34; width=&#34;600&#34; height=&#34;447&#34; alt=&#34;Auto-generated description: A browser window showcases a logo generator tool with a blue logo on the left labeled TEST AP-1 and options for customization above.&#34;&gt;
&lt;p&gt;It pretty much did what it said on the tin: when I changed the value of upper or lower, or changed the background colour, the canvas would redraw showing a different preview. Clicking &amp;ldquo;Download PNG&amp;rdquo; would allow me to save it as an image.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250913-090258.png&#34; width=&#34;600&#34; height=&#34;447&#34; alt=&#34;Auto-generated description: A logo design interface with fields for upper and lower text, a background color menu, and a Download PNG button is displayed in a browser window.&#34;&gt;
&lt;p&gt;On the whole, what I received was pretty good. It&amp;rsquo;s not perfect — there&amp;rsquo;s no styling; orange is a bit too close to amber, as is green and teal; and the alignment of the bottom line is slightly off. But it&amp;rsquo;s a pretty decent start.&lt;/p&gt;
&lt;p&gt;There were some things I wanted to change, so I went in myself. I found a nice looking stylesheet called &lt;a href=&#34;https://picocss.com&#34;&gt;Pico CSS&lt;/a&gt;, which I&amp;rsquo;ve applied. Normally I would&amp;rsquo;ve turned to &lt;a href=&#34;https://simplecss.org&#34;&gt;simple.css&lt;/a&gt;, but I&amp;rsquo;ve been using that a lot and I wanted to try a nice, fresh look. I also adjusted the vertical positioning of the lower row in the icon, added support for single line icons, and adjusted the colours a little.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250913-090305.png&#34; width=&#34;600&#34; height=&#34;435&#34; alt=&#34;Auto-generated description: A logo creation interface is displayed with options to input text, select background color, and download a PNG file.&#34;&gt;
&lt;p&gt;The whole thing looks quite nice now, and I&amp;rsquo;m quite happy with the icons it produces:&lt;/p&gt;
&lt;img class=&#34;block-center&#34; src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250912-220408.png&#34; width=&#34;212&#34; height=&#34;212&#34; alt=&#34;Auto-generated description: Four colored squares labeled Dev, Test, Preprod, and Prod represent different stages of a deployment pipeline.&#34;&gt;
&lt;p&gt;I&amp;rsquo;m still working out how these AI coding agents will fit into my life. I&amp;rsquo;ve been using Claude Code occasionally, and that&amp;rsquo;s been a good fit for projects where I&amp;rsquo;d prefer to be the driver. But there are occasional things that I&amp;rsquo;d like to see exist but are a little too trivial to spend the time on. In those cases, I may not have built it at all; or I would&amp;rsquo;ve built it and then immediately regret spending the time doing so it. And that might be where Codex could be useful: it provides a good starting point, which I can then refine.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in trying this tool out, &lt;a href=&#34;https://tools.lmika.app/freelens-logo/&#34;&gt;you can find a link to it here&lt;/a&gt;. The source &lt;a href=&#34;https://github.com/lmika/webtools&#34;&gt;you can find on GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/09/finally-indeed.html</link>
      <pubDate>Tue, 09 Sep 2025 08:41:44 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/09/finally-indeed.html</guid>
      <description>&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/share-3492805009512651338.png&#34; width=&#34;600&#34; height=&#34;500&#34; alt=&#34;A Bluesky modal announcing the released of saved posts, along with a post humorously suggests creating a Saturday that follows immediately after Monday, displayed above a section for saving posts. Modal message is as follows: Finally! Keep track of posts that matter to you. Save them to revisit any time.&#34;&gt;
&lt;p&gt;Finally indeed.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/07/ah-macoss-lockeddown-nature-strikes.html</link>
      <pubDate>Sun, 07 Sep 2025 12:14:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/07/ah-macoss-lockeddown-nature-strikes.html</guid>
      <description>&lt;p&gt;Ah, MacOS&amp;rsquo;s locked-down nature strikes again! Was testing the CI/CD build for Dequoter and after downloading the artefact and attempting to open it, I got this warning message:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250907-101030.png&#34; width=&#34;308&#34; height=&#34;298&#34; alt=&#34;Auto-generated description: A warning dialog box states that Dequoter is damaged and suggests moving it to the Bin, with options to cancel or proceed.&#34;&gt;
&lt;p&gt;Turn&amp;rsquo;s out it was being quarantined by MacOS, and &lt;a href=&#34;https://apple.stackexchange.com/a/328207&#34;&gt;these instructions resolved the issue&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xattr -dr com.apple.quarantine &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/Applications/Your Application.app&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The binaries not notarised so I wasn&amp;rsquo;t expecting it to work out of the box. I was hoping that it would do that thing where the app will be listed in settings and I can allow it to launch from there, but I guess there&amp;rsquo;s something about where this file came from that was too much from MacOS. Ah well, I can live with this for the short term.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/07/speaking-of-dequoter-in-celebration.html</link>
      <pubDate>Sun, 07 Sep 2025 11:21:44 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/07/speaking-of-dequoter-in-celebration.html</guid>
      <description>&lt;p&gt;Speaking of Dequoter, in &amp;ldquo;celebration&amp;rdquo; of the upcoming release of MacOS 26, I styled the command palette a little, adding a transmissive blur to give it a glass effect:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250907-101817.png&#34; width=&#34;600&#34; height=&#34;478&#34; alt=&#34;Auto-generated description: A text editor on a computer screen shows a command tool over a block of text, with options like Unquote and Format JSON.&#34;&gt;
&lt;p&gt;It&amp;rsquo;s a shame I can&amp;rsquo;t style the options of the select element. It would be nice to increase their margins a bit.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Dequoter — Something Different Today</title>
      <link>https://lmika.org/2025/09/06/devlog-dequoter-something-different-today.html</link>
      <pubDate>Sat, 06 Sep 2025 17:10:04 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/06/devlog-dequoter-something-different-today.html</guid>
      <description>&lt;p&gt;When it comes to hobby projects, I&amp;rsquo;m trying to reduce the number of things I build for work. But I&amp;rsquo;ve decided to revisit one that I&amp;rsquo;ve abandoned, given the frequent need for this. The issue is simple: I have a JSON data structure that is quoted within a string, and I want to &amp;ldquo;unquote&amp;rdquo; it and filter the JSON so that I can read it.&lt;/p&gt;
&lt;p&gt;So I thought I&amp;rsquo;d spend today restarting the Dequoter project. The best way to describe it is &lt;a href=&#34;https://github.com/IvanMathy/Boop&#34;&gt;a Boop clone&lt;/a&gt;. I like Boop, I use Boop. And I know Boop is extendable, so I could implement any processor I need using JavaScript. But I&amp;rsquo;m not a huge fan of JavaScript, and Go has the function I need &lt;a href=&#34;https://pkg.go.dev/strconv@go1.25.1#Unquote&#34;&gt;right there in the standard library&lt;/a&gt;. And with most things I encounter, I want to build my own. I know I shouldn&amp;rsquo;t, and I&amp;rsquo;ve been better at resisting. But I&amp;rsquo;d figured this could be a nice little distraction. And I thought I could get much of what I need done in a morning.&lt;/p&gt;
&lt;p&gt;So I started a new &lt;a href=&#34;https://wails.io&#34;&gt;Wales project&lt;/a&gt; and after about 3 hours, I had a scratchpad with an invokable command palette that I can use to dequote a string and format a JSON object:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-09-06-at-15.57.26.mp4&#34; poster=&#34;https://lmika.org/uploads/2025/ecf16f805a.png&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Granted, it&amp;rsquo;s not much for 3 hours. While I did use Claude Code a little, much of this was hand rolled. And I spent a lot of time learning the ins and outs of &lt;a href=&#34;https://codemirror.net&#34;&gt;Code Mirror&lt;/a&gt;. This is why I wanted to use Wales this time around. The &lt;a href=&#34;https://lmika.org/2024/07/20/continuing-my-exploration.html&#34;&gt;previous run at this&lt;/a&gt; used &lt;a href=&#34;https://fyne.io/&#34;&gt;Fyne&lt;/a&gt;, which was really nice to use, but the available widgets were quite limited. The nice thing about Wales is that I can build the UI in HTML and it uses the built-in HTML renderer from the OS: no need to bundle Chrome. The frontend is mainly vanilla HTML and JavaScript, although I am using &lt;a href=&#34;https://stimulus.hotwired.dev&#34;&gt;Stimulus&lt;/a&gt; to help with some of the controls. The backend is, of course, Go.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-09-06-at-15.46.43.png&#34; width=&#34;400&#34; height=&#34;572&#34; alt=&#34;Auto-generated description: Window displaying file information for an application named dequoter, including details like size, location, and version.&#34;&gt;
&lt;figcaption&gt;Final build size is 8.4 MB.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;By the end of this morning&amp;rsquo;s session, what I had was not pretty, but it&amp;rsquo;s functional and it already does what I need it to do. I do have some plans for integrating this with UCL, and using that to define processors. That should hopefully leave all the heavy core stuff to be built near the beginning, and that could sit pretty while the extensions are added in a nicer language. Anyway, more on this if this goes anywhere.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/05/i-saw-someone-online-mention.html</link>
      <pubDate>Fri, 05 Sep 2025 12:15:05 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/05/i-saw-someone-online-mention.html</guid>
      <description>&lt;p&gt;I saw someone online mention &lt;a href=&#34;https://www.zo.computer&#34;&gt;Zo Computer&lt;/a&gt; so I though I&amp;rsquo;d give it a try. Asked it to produce a Go function based on &lt;a href=&#34;https://til.computer/PostgreSQL/PostgreSQL,-pgx,-sqlc-and-bytea&#34;&gt;one of my blog posts&lt;/a&gt;, since I needed the same thing in a different project. I saw it open the blog post and generate the function in about 5 seconds.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250905-110828.png&#34; width=&#34;600&#34; height=&#34;348&#34; alt=&#34;Auto-generated description: A computer screen displays a coding interface with a task description and a code editor, focusing on implementing a Go function related to a PostgreSQL database.&#34;&gt;
&lt;p&gt;Granted that this is hardly groundbreaking. It&amp;rsquo;s using GPT 4.1 mini, so it&amp;rsquo;s likely I could&amp;rsquo;ve done the same thing straight from ChatGPT. But I think it&amp;rsquo;s a good first step in seeing what this service is capable of.&lt;/p&gt;
&lt;p&gt;I also wonder if the model is actually consuming the post. I have nothing to support this other than doubt that the post would be in GPT 4.1&amp;rsquo;s training data. The URL can&amp;rsquo;t be more than a month old.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/03/starting-to-work-on-the.html</link>
      <pubDate>Wed, 03 Sep 2025 22:27:26 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/03/starting-to-work-on-the.html</guid>
      <description>&lt;p&gt;Starting to work on the background tiles. This is what I have so far. I hope it&amp;rsquo;s not too busy or distracting.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250903-212503.png&#34; alt=&#34;Auto-generated description: A pixelated video game scene features a character in knight armor navigating a stone brick environment with a wooden crate and a collectible item.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/02/what-is-this-a-ui.html</link>
      <pubDate>Tue, 02 Sep 2025 13:31:59 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/02/what-is-this-a-ui.html</guid>
      <description>&lt;p&gt;What is this?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250902-123014.png&#34; width=&#34;600&#34; height=&#34;393&#34; alt=&#34;Auto-generated description: A software interface for Insomnia is displayed, showing an API development environment with a navigation panel on the left.&#34;&gt;
&lt;p&gt;A UI for ANTS?! 😼&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/09/02/its-not-unheard-of-to.html</link>
      <pubDate>Tue, 02 Sep 2025 08:58:28 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/02/its-not-unheard-of-to.html</guid>
      <description>&lt;p&gt;It&amp;rsquo;s not unheard of to have animals, usually kangaroos, on our line near the down end where it&amp;rsquo;s quite bushy. But this is well within the inner city. I wonder what it could be. 🤔&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/screenshot-20250902-075209.png&#34; width=&#34;600&#34; height=&#34;388&#34; alt=&#34;A notification from Metro Notify alerts about a delay on the Hurstbridge line due to an animal on the tracks at West Richmond.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Project — Bricks in Level 2-3 Laid</title>
      <link>https://lmika.org/2025/09/01/devlog-godot-project-bricks-in.html</link>
      <pubDate>Mon, 01 Sep 2025 22:31:56 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/09/01/devlog-godot-project-bricks-in.html</guid>
      <description>&lt;p&gt;Just a quick update today. I&amp;rsquo;ve finished all the brickwork in level 2-3. And it didn&amp;rsquo;t go too badly. Made one significant mistake which would&amp;rsquo;ve involved a lot of rework, that I patched up with some single tiles:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250901-212752.png&#34; width=&#34;600&#34; height=&#34;722&#34; alt=&#34;Auto-generated description: Two sections of a pixelated game map featuring stone walls, an arched door, and orange blocks resembling platforms.&#34;&gt;
&lt;figcaption&gt;Top: the mistake. Bottom: the fix.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Doing the rest of it was pretty dreary work. Godot does have some tools to make this easier, but there was no getting around the level of care needed to place the bricks correctly. But it&amp;rsquo;s all pretty much done now. And just for comparison to the &lt;a href=&#34;https://lmika.org/2025/07/26/devlog-godot-project-level-and.html&#34;&gt;before screenshots&lt;/a&gt; I took when I started, here&amp;rsquo;s how how the level looks now:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250901-211644.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated video game scene shows a knight on a platform with lava below, stone blocks, and crates to the right.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250901-211652.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated video game scene shows a character in armor navigating a platform level with brick walls, crates, and spiked obstacles.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250901-211659.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated character in knight armor stands on a brick platform with a gray background, part of a retro-style video game.&#34;&gt;
&lt;p&gt;There&amp;rsquo;s still plenty of work. The background is not yet done, nor are any of the pickups placed. The HUD needs updating to show the key gems the player has, and I also need to repair some dodgy mechanics around moving platforms. But I guess that&amp;rsquo;s just a matter of plowing ahead.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Shutting Down Nano Journal</title>
      <link>https://lmika.org/2025/08/30/devlog-shutting-down-nano-journal.html</link>
      <pubDate>Sat, 30 Aug 2025 16:34:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/30/devlog-shutting-down-nano-journal.html</guid>
      <description>&lt;p&gt;With the move to Obsidian for my journalling needs, I shut down my bespoke journalling web-app. I deployed it on 26th August 2024, which makes it just over a year old. I did start using Obsidian on the 20th though, so it didn&amp;rsquo;t quite make it the entire year. Even so, not bad for something hand made and somewhat neglected. Most things I eventually abandon last way less than that.&lt;/p&gt;
&lt;p&gt;Anyway, here are some screenshots of the final version, just before I shut it down:&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250830-115806.png&#34;
     
        alt=&#34;Auto-generated description: A digital journal interface features a text box with the prompt What&amp;#39;s been happening today and buttons labeled Attach and Save.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The landing page, with a full-width post text area.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250830-115815.png&#34;
     
        alt=&#34;Auto-generated description: A journal entry interface with a textbox discussing layout issues and an attachment option is shown.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  A post with attachments.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250830-115840.png&#34;
     
        alt=&#34;Auto-generated description: A journal webpage displays a list of dated entries from May to August 2025, with links to each entry.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Clicking &amp;#39;Posts&amp;#39; will bring up a chronological list of posts from the last 6 months. Those with a paperclip are ones with attachments.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250830-115851.png&#34;
     
        alt=&#34;Auto-generated description: A digital note dated May 17, 2025, expresses frustration about relying on others for problem-solving and a desire for improvement.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  An example of a viewing a post. Attachments would appear below the body.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;You can compare this with some earlier screenshots documented in these posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://lmika.org/2024/08/22/i-enjoyed-reading.html&#34;&gt;22 August 2024&lt;/a&gt;: Development work started&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lmika.org/2024/09/15/weekly-update-sept.html&#34;&gt;15 September 2024&lt;/a&gt;: Addition of attachments and titles&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lmika.org/2024/10/04/im-not-really.html&#34;&gt;4 October 2024&lt;/a&gt;: Adding Git synchronisation of entries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I guess you can describe the ascetics of this project as the &amp;ldquo;minimum amount to get working.&amp;rdquo; That was true of the backend too, which was somewhat rushed and difficult to maintain. That might be why I never really gave this a lot of love as other projects like Blogging Tools. But I added some features I that I thought were neat: such as attachments, and keeping a copy of a draft in the browser&amp;rsquo;s local storage before it was sent to the server.&lt;/p&gt;
&lt;p&gt;Anyway, I&amp;rsquo;m now in the middle of migrating the old posts over to Obsidian. One thing I&amp;rsquo;m glad I added was Git synchronisation. And given that entries were plain markdown files, migration was simply a matter of checking out the repository and moving the posts across. Easy enough work, although I am taking my time. There&amp;rsquo;s a bit of reliving involved with moving the posts over, and I&amp;rsquo;m approaching a period where some pretty sad things happened.&lt;/p&gt;
&lt;p&gt;Anyway, that was Nano Journal. Good at what it needed to do.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Project — Level 2-3 Update</title>
      <link>https://lmika.org/2025/08/26/devlog-godot-project-level-update.html</link>
      <pubDate>Tue, 26 Aug 2025 23:27:58 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/26/devlog-godot-project-level-update.html</guid>
      <description>&lt;p&gt;Been working on level 2-3 these past few weeks. And it&amp;rsquo;s a large one: 4,720 x 2,928 units. There are longer levels but those tend to be quite horizontal, unlike this one which feels like a mixture of the two dimensions:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250826-214656.png&#34; width=&#34;600&#34; height=&#34;318&#34; alt=&#34;Auto-generated description: A pixel art depiction of a pyramid interior displayed within a game development software interface.&#34;&gt;
&lt;p&gt;Reached a milestone this evening: the critical path has been built. And yeah, it ended up pretty much being a standard key-hunt. I did have an idea for having more puzzle elements, like requiring the player to work out combinations based on key colour, but given the length one has to travel to get the necessary keys, it felt like a hit to the pacing. I figured it would be more interesting to vary the mechanics in each zone the keys are located. They&amp;rsquo;re not complicated mechanics, but they did reach the limit of what I had in my toolbox. It got to the point where I started adding the classics, like dart shooters which fired projectiles.&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250826-214959.png&#34;
     
        alt=&#34;Auto-generated description: A pixelated video game scene features a knight character standing on a wooden crate within a brick-walled level, avoiding swinging spiked traps.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Pick up these gems…
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250826-214933.png&#34;
     
        alt=&#34;Auto-generated description: A pixel art video game scene features a knight character on a brick platform navigating a level with ladders and a pickaxe-like object.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  …and place them on this holders to activate things. And yes, I stole this directly from Commander Keen.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250826-215012.png&#34;
     
        alt=&#34;Auto-generated description: A pixel art-style video game features a small knight character on green platforms above a wavy brown surface with obstacles and coins on the screen.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Given that this is in the desert world, the sand spinners make an appearance.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250826-214944.png&#34;
     
        alt=&#34;Auto-generated description: A pixelated video game scene features a knight character navigating platforms and obstacles made of spikes and bricks.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The new dart shooters and the projectiles they fire.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;There are a few mechanics I wanted the player to learn. Trying to telegraph that via gameplay is not easy, but I&amp;rsquo;m hoping it will comes through. Some approaches I&amp;rsquo;m trying are slight cues in colour and making sure interaction indicators are clear. And also level geometry, such as moving the player through one-way gates and making it obvious that all they have to proceed is right in front of them.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also decided to move away from paid checkpoints, where you need to pay 5 coins to save your progress. I&amp;rsquo;ve replaced these with hidden checkpoints, of which this level has a few. I think given the size, and that some backtracking is involved, it makes more sense being quite generous with checkpoints in this level. I think I&amp;rsquo;ll keep the 5 coin toll gates though for other things.&lt;/p&gt;
&lt;p&gt;Work remaining on this level is prettying it up (it&amp;rsquo;s still rather barebones) plus adding pickups and maybe some bonuses. Painting in the brick tiles will take a while, but could be a nice exercise in calming work when one can switch off their brain somewhat. I also need to make the background tiles and (ugh) the actual level backdrop. Not quite sure what that will be. Something deserty no doubt.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/08/22/always-something-with-npm-isnt.html</link>
      <pubDate>Fri, 22 Aug 2025 10:19:30 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/22/always-something-with-npm-isnt.html</guid>
      <description>&lt;p&gt;It&amp;rsquo;s always something with NPM, isn&amp;rsquo;t it:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250822-091648.png&#34; width=&#34;600&#34; height=&#34;196&#34; alt=&#34;Auto-generated description: A terminal interface shows a series of error messages related to installing dependencies, including error code ENOTEMPTY and directory issues.&#34;&gt;
&lt;p&gt;Here&amp;rsquo;s an idea: why don&amp;rsquo;t YOU rename that directory, NPM?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools — All About Images</title>
      <link>https://lmika.org/2025/08/10/devlog-blogging-tools-all-about.html</link>
      <pubDate>Sun, 10 Aug 2025 22:30:40 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/10/devlog-blogging-tools-all-about.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been working on Blogging Tools on-and-off the last few weeks, mainly around images and image management. They&amp;rsquo;re not super exciting in their own right so I&amp;rsquo;d figure I&amp;rsquo;ll briefly write about them here.&lt;/p&gt;
&lt;p&gt;The first major addition is the ability to save images to the server from the Image Tool app. There are some composition processors available and if one wanted to pre-process the image in some way, they would have to download the pre-processed image and upload it again for composition. This change will hopefully reduce the need for this back-and-forth. The download button has been replaced within the Image Tool app with a Save button, which will upload the processed image to the server.&lt;/p&gt;
&lt;p&gt;Saved images can be recalled from the processor by using a new picker. This replaced the Load image by URL option, which I never really used (although I may add it back within the Saved Image app).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212554.png&#34; width=&#34;600&#34; height=&#34;368&#34; alt=&#34;Auto-generated description: A grid layout displays images of a pigeon, crow, lion, tiger, and a cartoon fish on a blogging tool interface.&#34;&gt;
&lt;p&gt;There is also a new Saved Images app which allows one to browse the saved image, file them into categories, and download them to the local file system.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212627.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Auto-generated description: A webpage titled Blogging Tools displays five saved images including a pigeon, a crow, a lion, a tiger, and a yellow fish in a grid layout.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212648.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Auto-generated description: A stuffed toy of a pigeon is placed on a table, visible within a web interface.&#34;&gt;
&lt;p&gt;Now those with keen eyes may notice the quality of the saved images in the screenshots above. That&amp;rsquo;s because the second change I made was to integrate calls to an image generator. There was no reason for doing this other than to try out the API, and I haven&amp;rsquo;t really used it for anything other than make test images. The API I&amp;rsquo;m using is Flux 1 provided by &lt;a href=&#34;https://together.ai/&#34;&gt;together.ai&lt;/a&gt;, but there&amp;rsquo;s also a partially-functional version of Gemini&amp;rsquo;s API too (the API is region locked and is not available in Germany, which is where Blogging Tools is running).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212658.png&#34; width=&#34;600&#34; height=&#34;387&#34; alt=&#34;Auto-generated description: A webpage titled Blogging Tools features an Image Generator section with a prompt about a cartoon person leaning out a window in comedic anger and buttons to Generate and Clear Completed.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212705.png&#34; width=&#34;600&#34; height=&#34;387&#34; alt=&#34;Auto-generated description: A user interface titled Blogging Tools with options for apps, notices, and settings, includes a request input box, a provider selection, and a generate button.&#34;&gt;
&lt;p&gt;Saved Images are build on the existing Files subsystem, which has been relegated to a new Settings section, the third change I&amp;rsquo;ve made to Blogging Tools. This allows one to manage files, see running jobs, and managed sessions from the system. That section may expand in time as I think of things I&amp;rsquo;d want to change without redeploying the app (most of the config is managed using environment variables).&lt;/p&gt;
&lt;p&gt;The last change was the addition of a new notification type. I&amp;rsquo;ve added &lt;a href=&#34;https://help.micro.blog/t/photo-collections/3366&#34;&gt;photo collections&lt;/a&gt; to my blog a few months ago, but I never remember to actually file photos into any of them. My workflow with making a photo post usually ends at the point I hit &amp;ldquo;publish&amp;rdquo;, and as far as I can tell, there&amp;rsquo;s no way to set the category of the image from the new post screen.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;ve added a new notification type to help me with this. When the RSS feed of my blog is polled, and a new post with the &amp;ldquo;Photos&amp;rdquo; category is found, the first 4 images are extracted and presented as a notification asking if I want to file the photos into a photo category.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250810-212755.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Auto-generated description: A blogging tools interface displays a notice with an image of a parked red car with its hood open next to a white car.&#34;&gt;
&lt;p&gt;This also worked for posts you add to the &amp;ldquo;Photos&amp;rdquo; category via the notification subsystem itself. Both act on the same RSS feed and setting the category of a post to &amp;ldquo;Photos&amp;rdquo; will clear the fetch history of that item so that it can run through the processors again.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve decided to use notifications for this, rather than add something to do this automatically, as I do want to go through the candidate photos first. But there are some limitations to this approach. For one, the notification system only allows one user selection per notification, making it impossible to add a photo to more than one collection. And to keep the number of notifications low, only the first 4 images are presented, with the rest being ignored. I have some ideas of how this could be improved in the future — probably by adding a separate app — but for now, I think I could live with taking corrective action manually.&lt;/p&gt;
&lt;p&gt;One last think about this new category processor: it&amp;rsquo;s activated by a change to the RSS feed, but it scans for images using the original post retrieved from Micro.blog&amp;rsquo;s micropub API. The reason for that I&amp;rsquo;ve enabled the CDN option, and the image URLs which use the CDN do not work with the collections API. It took me a while before I figured this out, mainly because I wasn&amp;rsquo;t using a CDN on my test blog and that worked perfectly. Something to keep in mind for anyone else using this API.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/08/07/i-gotta-say-im-not.html</link>
      <pubDate>Thu, 07 Aug 2025 08:05:39 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/07/i-gotta-say-im-not.html</guid>
      <description>&lt;p&gt;I gotta say, I&amp;rsquo;m not digging this white-on-white button motif in Liquid Glass. It looks buggy and unfinished. A little dated too: it reminds me of the late 2000&amp;rsquo;s when box-shadows were added to CSS 3 and websites were experimenting with using shadows as borders.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250807-070110.png&#34; alt=&#34;Two digital interface screenshots show a mobile email inbox with 17 unread messages and a browser displaying about:blank, both with a timestamp of 6:51 am and 6:59 am, respectively.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Kicking the Tyres of Ollama&#39;s Native App</title>
      <link>https://lmika.org/2025/08/06/kicking-the-tyres-of-ollamas.html</link>
      <pubDate>Wed, 06 Aug 2025 17:43:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/06/kicking-the-tyres-of-ollamas.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve seen a &lt;a href=&#34;https://www.manton.org/2025/08/04/parker-ortolani-blogs-about-the.html&#34;&gt;few people online&lt;/a&gt; talk about &lt;a href=&#34;https://ollama.com/blog/new-app&#34;&gt;the new Ollama native macOS app&lt;/a&gt;. I&amp;rsquo;ve not used Ollama&amp;rsquo;s CLI tools myself, and have very limited experience with running models locally. I think I tried a couple of years ago, and gave up at the step when they asked me to setup a Python virtual environment. Since then I was always under the impression that setting up LLMs to run locally would be painful, and while I liked the idea of doing so, it wasn&amp;rsquo;t enough to go through that pain again. So hearing about Ollama packaging all this in a native app piqued my interest, and I thought I&amp;rsquo;d give it a go.&lt;/p&gt;
&lt;p&gt;Install was as expected: just download a DMG and add to Applications. I was half expecting the package to be huge to accomodate the models, but the download doesn&amp;rsquo;t include the models themselves, which is fair enough given that there are a few to choose from.&lt;/p&gt;
&lt;p&gt;First launched revealed a very ChatGPT-esk experience, making it absolutely clear how this app worked. I poked around a little trying to find the place where I can download a model, but I couldn&amp;rsquo;t find anything. I then tried posting a question and quickly found out that models are downloaded on demand.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250806-084256.png&#34; width=&#34;600&#34; height=&#34;520&#34; alt=&#34;Auto-generated description: A minimalistic chat window features a simple cartoonish character and a text input field.&#34;&gt;
&lt;p&gt;It was then a matter of trying out some of these models. First test was with Gemma 3:4b. I asked it a question around the tongue-twister &amp;ldquo;see shells sea shells by the sea shore&amp;rdquo;. The fact that it actually produce a result was impressive, yet the quality of the answer was questionable:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250805-083942.png&#34; width=&#34;600&#34; height=&#34;494&#34; alt=&#34;Auto-generated description: A humorous chat exchange discussing a popular saying about women selling seashells at the beach.&#34;&gt;
&lt;p&gt;For comparison, here&amp;rsquo;s the same question posed to ChatGPT:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250805-084000.png&#34; width=&#34;600&#34; height=&#34;310&#34; alt=&#34;Auto-generated description: A ChatGPT interaction shows a question about a popular saying regarding a female at a beach selling sea-shells, followed by the answer with the tongue twister, She sells sea-shells by the sea-shore.&#34;&gt;
&lt;p&gt;So this model struggled a little, probably because it was on the small size (I&amp;rsquo;m assuming the &amp;ldquo;4b&amp;rdquo; refers to the number of parameters. 4 billion?). I didn&amp;rsquo;t get a good gauge as to how slow the response was. It was slower, but not as slow as I was expecting. Plus it was coloured by the time it took to download the model, which itself was not fast (maybe 30 minutes to an hour, although I wasn&amp;rsquo;t on a fast connection).&lt;/p&gt;
&lt;p&gt;I then tried the same question with Deepseek R1:8b, and was greeted with a reminder that this is not a Western trained model. But after some clarification it got there in the end. Ollama actually displayed the reasoning steps as the model was going through it&amp;rsquo;s inference, which is a nice touch.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250805-105958.png&#34; width=&#34;600&#34; height=&#34;538&#34; alt=&#34;Auto-generated description: A text-based conversation discussing the saying She sells seashells by the sea shore with an additional note about cultural differences.&#34;&gt;
&lt;p&gt;I could go on and try Qwen 3, but you get the idea. This is not really about the models (okay, it&amp;rsquo;s sort of about the models, but I&amp;rsquo;m hardly putting them through the paces).&lt;/p&gt;
&lt;p&gt;So on the whole, I found the app itself is quite pleasant to use. The minimal aesthetics make it very approachable for someone who only had experience using ChatGPT. I also like that you can go back through your chat and edit past questions. Being able to &amp;ldquo;alter the context&amp;rdquo; like this is something I wish the online AI chat apps support, particularly when the model start going down the wrong path.&lt;/p&gt;
&lt;p&gt;At the same time, I do wish I have a bit more control over the experience. I&amp;rsquo;m not a user of Ollama&amp;rsquo;s CLI tool so I don&amp;rsquo;t know what&amp;rsquo;s possible but some things I would like to see added down the line:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A way to manage model downloads outside of the chat window.&lt;/li&gt;
&lt;li&gt;A way to configure the system prompt. I actually don&amp;rsquo;t know if Ollama&amp;rsquo;s applying one, but if it is, it would be nice to see/modify it.&lt;/li&gt;
&lt;li&gt;A way to export the chat transcript.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, would I continue using this? Honestly, probably not, mainly because of performance. I&amp;rsquo;m not sure the computer I&amp;rsquo;m using (a 2020 MacBook Pro M1 with 16 GB RAM) is powerful enough to be competitive to the online models. Maybe if I had the latest and greatest hardware, with as much memory and significantly more disk space, it would be a viable contender. I do have access to something a little newer (Mac Mini M2) so I maybe I&amp;rsquo;ll try it out there. It would be interesting to see how the &lt;a href=&#34;https://simonwillison.net/2025/Aug/5/gpt-oss/#atom-everything&#34;&gt;OpenAI&amp;rsquo;s new open weight models&lt;/a&gt;, which were release while I was writing this, will perform.&lt;/p&gt;
&lt;p&gt;But that said, I&amp;rsquo;m quite impress that I was able to run these models at all. I have heard of others running local models for a while now, and I like the Ollama makes this easy to do so, with a nice, approachable native app.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Moving TIL Computer To Quartz</title>
      <link>https://lmika.org/2025/08/04/moving-til-computer-to-quartz.html</link>
      <pubDate>Mon, 04 Aug 2025 23:49:57 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/04/moving-til-computer-to-quartz.html</guid>
      <description>&lt;p&gt;Moved &lt;a href=&#34;https://til.computer&#34;&gt;TIL Computer&lt;/a&gt; over to a new technology stack. Yes, again. This frickin&amp;rsquo; site, and it&amp;rsquo;s spiritual predecessor, has seen a few tech stacks transitions recently it&amp;rsquo;s surprising that I haven&amp;rsquo;t significantly lost anything (apart from the post dates). The main reason is that I can&amp;rsquo;t decide if this should be a blog or a wiki. The site contains the accumulation of lessons learnt and other bits of knowledge acquired as part of my software engineering job, so having a way to quickly access something is moderately important. The pages themselves are relatively long-lived, and somewhat evergreen, so there&amp;rsquo;s not a huge temporal access: one could argue that an RSS feed would be unnecessary. However, I do want that buzz from seeing something I publish show up in an RSS reader, so one would be nice. Hence the reason why this site was principally a blog for most of it&amp;rsquo;s existence.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s why my interest was piqued when I saw Jessica Smith&amp;rsquo;s post on &lt;a href=&#34;https://www.jayeless.net/blog/switched-to-quartz&#34;&gt;how she moved her site to Quartz&lt;/a&gt;. After some investigation into &lt;a href=&#34;https://quartz.jzhao.xyz&#34;&gt;Quartz&amp;rsquo;s features&lt;/a&gt;, including how well it works with Obsidian, it seemed enough of an improvement to try out.&lt;/p&gt;
&lt;p&gt;So I took an export of the old site, previously hosted on Micro.blog, and imported the markdown files into a new Obsidian vault. It was also an opportunity to finish fixing up the posts that were still encoded in HTML from the previous export, which I didn&amp;rsquo;t get around to fixing when I imported them into Micro.blog:&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-220856.png&#34;
     
        alt=&#34;Auto-generated description: A blog post features a code snippet for printing numbers 0 to 9 using range in Go, dated Wednesday, July 16, 2025.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The home page, which posts listed in reverse chronological order.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-220904.png&#34;
     
        alt=&#34;Auto-generated description: An article from TIL Computer provides a guide on using cron to run scheduled jobs, focusing on running jobs with Bash to ensure user profiles are loaded correctly.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  An example of a post.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221001.png&#34;
     
        alt=&#34;Auto-generated description: A webpage details the process of decoding binary Protobuf messages using a protoc command.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Another post. I will admit I will miss the fonts used in this template.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-220911.png&#34;
     
        alt=&#34;Auto-generated description: A website&amp;#39;s archive page displays entries from 2025 about programming topics like Go and Temporal workflows.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The archive. The categories would go on to be the top-level folders in the Quartz site.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-220929.png&#34;
     
        alt=&#34;Auto-generated description: A search results page displays articles related to git with titles and publication dates.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The search, which is actually quite good.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221009.png&#34;
     
        alt=&#34;Auto-generated description: A webpage titled Cheatsheets lists resources on Linux commands, Git committing, and Git history.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  These cheatsheets never really developed into anything. The goal was to concatenate a collection of titleless posts into one large page. But I never got round to building the template for that, which meant I never wrote any posts that would fit that model.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The Obsidian vault was then added to a Git repository, which&amp;rsquo;ll get pushed to my Forgejo instance when the site is to be republished. Doing so will run a CI/CD pipeline which will download Quartz, prepare a new workspace, copy the Obsidian vault into the &amp;ldquo;content&amp;rdquo; directory, copy the config and layout files, build the site, and then push it to Netlify. This is probably not the recommended approach, but I&amp;rsquo;m very interested in making sure the Git repository is as close to the original Obsidian vault as possible. I plan to relay on Obsidian and it&amp;rsquo;s vault sync for writing the site&amp;rsquo;s content, so anything that could be done to avoid disrupting that is welcomed.&lt;/p&gt;
&lt;p&gt;The layout isn&amp;rsquo;t too far from the default, apart from some light changes to the colouring, font, and layout. I already like the wiki-style sidebar on the left, which would make finding things easier. The fact that I was more-or-less limited to reverse chronological posts list was one of the downsides of the blog-style approach (well, that&amp;rsquo;s not entirely true: I could&amp;rsquo;ve adjusted the template to be more wiki-like. I just never got around to doing so).&lt;/p&gt;
&lt;p&gt;I also simplified the information design a little. The original idea I had was to separate guides from references and &amp;ldquo;cheatsheets,&amp;rdquo; a post type that was never really defined well. But I found that I preferred just lumping the posts into folders sitting at the top-level based on the technology. Pretty unsophisticated, &amp;rsquo;tis true, but given that there&amp;rsquo;s only around 60 pages there, I think it&amp;rsquo;s just enough to know where things are while keeping things simple. I&amp;rsquo;m also hope to use the search more often, a feature Quartz comes with out of the box.&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221330.png&#34;
     
        alt=&#34;Auto-generated description: A webpage from TTL Computer with a welcome message and navigation sidebar showcasing various tech topics.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The new Quartz site, showing off the current… let&amp;#39;s say… &amp;#39;minimalist&amp;#39; index page.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221345.png&#34;
     
        alt=&#34;Auto-generated description: A digital document or webpage titled How To REALLY Use Cron To Run Scheduled Jobs with navigation sidebar and body text explaining cron job scheduling.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  An example of a page. One thing I do like about this template is that a table of contents will appear on the right if the window is wide enough.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221358.png&#34;
     
        alt=&#34;Auto-generated description: A computer interface displays a folder named Go containing a list of files or items with names and timestamps from August 2025.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Selecting a top-level folder will reveal the contents. I believe this is customisable, but this will do for me now.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250804-221411.png&#34;
     
        alt=&#34;Auto-generated description: A Git documentation webpage with a sidebar menu shows detailed instructions on committing, including a section for getting the number of Git commits to squash.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The search interface. Looks a little busy, but it definitely works. I would like to hook up a hot-key to open this up eventually.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;I may need to adjust the fonts a little more, and I am thinking of hooking up a hot-key to trigger the search box, but so far, I&amp;rsquo;m quite pleased by it. Granted it&amp;rsquo;s only been a couple of days, and I still have the backups of the old site if I want to back to this being a plain-old blog. But I think the Obsidian integration plus the more wiki-like architecture will suit this site better.&lt;/p&gt;
&lt;p&gt;Oh, and one last thing: it&amp;rsquo;s been almost a year that that site has been up, and good news: I don&amp;rsquo;t hate the name. That was the issue I had with it&amp;rsquo;s spiritual predecessor, and it got to a point where I wasn&amp;rsquo;t motivated to write on it anymore. I don&amp;rsquo;t have any of these feelings with this name. Nor do I hate the idea of writing technical stuff there and everything else here. I know this is not a concern for anyone else, but for a few years I struggled with the idea of keeping two separate sites and where things should go. With having a place to keep my technical knowledge separate and in a format that&amp;rsquo;s easier to navigate, I think those feelings are at rest.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/08/02/there-was-something-about-the.html</link>
      <pubDate>Sat, 02 Aug 2025 09:16:22 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/08/02/there-was-something-about-the.html</guid>
      <description>&lt;p&gt;There was something about the appearance of iOS 26 Safari WebViews I wasn&amp;rsquo;t too sure about, and now I know: there&amp;rsquo;s a material transition between the header and the web-page, but there&amp;rsquo;s nothing separating the two. No edge, no gradient. This looks unnatural and, dare I say, a little amateur.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250802-081137.png&#34; width=&#34;600&#34; height=&#34;243&#34; alt=&#34;Auto-generated description: A blurred web page showing techdirt.com and partial text related to a department.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/07/30/obsidian-plugin-daily-notes-editor.html</link>
      <pubDate>Wed, 30 Jul 2025 11:14:06 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/30/obsidian-plugin-daily-notes-editor.html</guid>
      <description>&lt;p&gt;🛠️ &lt;a href=&#34;https://github.com/Quorafind/Obsidian-Daily-Notes-Editor&#34;&gt;Obsidian Plugin: Daily Notes Editor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Displays all your daily notes in a single editor tab, much like Roam Research. This was a feature I liked about Roam, and when I first looked at Obsidian, I wish it had it. Trying it out on my personal vault, where the daily notes tend to be quite small.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250730-100340.png&#34; width=&#34;600&#34; height=&#34;400&#34; alt=&#34;Auto-generated description: A digital document features dated notes with navigation features, modal keypresses, and task lists, displayed in a note-taking application interface.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Some First Impressions of iPadOS 26 Public Beta</title>
      <link>https://lmika.org/2025/07/27/some-first-impressions-of-ipados.html</link>
      <pubDate>Sun, 27 Jul 2025 08:44:23 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/27/some-first-impressions-of-ipados.html</guid>
      <description>&lt;p&gt;Upgraded my iPad to the iOS 26 beta, just to see what this fuss over Liquid Glass was about. First impressions after about 30 minutes of use: hmm. Certain aspects about it do look nice — I like the effect it has on the launch bar and when moving an empty app folder over the changing background. And I can see what they&amp;rsquo;re trying to achieve. But there are some aspects I&amp;rsquo;m not too sure about.&lt;/p&gt;
&lt;p&gt;The glassy lettering style for the time on the lock screen makes it harder to see at a glance. Likewise for notifications with the glassy background. Both give the impression of something being there as long as you look carefully at the edges. It reminds me of that episode of Seinfield where Kramer found a windscreen on the road and thought to make a coffee table from it, with the other characters expressing skepticism that they wouldn&amp;rsquo;t be able to see it (&amp;ldquo;you&amp;rsquo;ll sense it&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;Another thing I&amp;rsquo;m not to sure about: the lack of definitive edges between colours in app icons, like the Photos app. They now make them look a little blurry from a distance, at least to my eyes:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/screenshot-2025-07-27-at-7.07.54am.png&#34; width=&#34;600&#34; height=&#34;316&#34; alt=&#34;Auto-generated description: Six colorful app icons on a red-orange background represent Home, Photos, Camera, Contacts, Maps, and Find My.&#34;&gt;
&lt;p&gt;Might be okay if all the icons were like this, but at this stage, when you compare it to an icon that has not been updated, like Obsidians, it just looks weird:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/screenshot-2025-07-27-at-7.29.37am.png&#34; width=&#34;600&#34; height=&#34;103&#34; alt=&#34;Auto-generated description: A row of application icons is displayed on a device&#39;s dock, including Safari, Photos, Settings, and several others.&#34;&gt;
&lt;p&gt;I don&amp;rsquo;t like what they&amp;rsquo;ve done to Safari either. The header is now taller and the address bar is now shorter. Shortening the address bar actually bothers me quite a bit. I don&amp;rsquo;t know why they had to do that, it&amp;rsquo;s not like they&amp;rsquo;re short on space. Granted I&amp;rsquo;m using Safari in landscape mode, which is how I generally use my iPad, but it&amp;rsquo;s not like the address needs to be the same size in both orientations. And it&amp;rsquo;s not like they don&amp;rsquo;t know how to do this: they did it in iOS 18.&lt;/p&gt;
&lt;p&gt;Oh, and ahh, yeah: contrast bugs in Safari&amp;rsquo;s header are still a thing:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/weekly-review-20250726-mandaris.png&#34; width=&#34;600&#34; height=&#34;136&#34; alt=&#34;Auto-generated description: A dark-themed webpage displays the name Mandaris with navigation buttons like About, Links, Photos, and Archive.&#34;&gt;
&lt;p&gt;Finally, popup menus in Safari: when you bring them up for a picker, the menu shoots off from the button to the right and then resizes to left to reveal the options. This feels buggy to me, or at least I hope it&amp;rsquo;s a bug as it&amp;rsquo;s a transition animation that I don&amp;rsquo;t care for. For something that I expect to just drop down, there&amp;rsquo;s a lot of unnecessary moving about.&lt;/p&gt;
&lt;p&gt;So yeah, first impressions are kinda mixed. I can now understand what the Apple pundits I listen to are talking about. And I do get the sense they may have bitten off more than they can chew here. Hopefully they can improve on this before the general release.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Project — Level 4-2 And Level 2-3</title>
      <link>https://lmika.org/2025/07/26/devlog-godot-project-level-and.html</link>
      <pubDate>Sat, 26 Jul 2025 16:47:11 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/26/devlog-godot-project-level-and.html</guid>
      <description>&lt;p&gt;I can&amp;rsquo;t quite remember how far I got into building of level 4-2 when I wrote my last Dev Log entry. Based on how I described it, I think I was still working on the bones of it: a level with little more than the tile-map. I got a fair bit of the level built since then. It now features three major zones, each with a different camera lock. The central mechanic of a tile layer that can be raised by hitting switches has been built, along with it&amp;rsquo;s variations within each zone. Pickups and enemies have now been placed. And decoration. A lot of decoration.&lt;/p&gt;
&lt;p&gt;I wish I had a screenshot of how it looked when I wrote that last entry, but here&amp;rsquo;s a shot of how it looks now:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250726-154404.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated platform game scene features a character navigating underground, with obstacles and items scattered around.&#34;&gt;
&lt;p&gt;I&amp;rsquo;m quite happy with how the mid-layer tiles add some interest to the aesthetics. But while thinking of the actual background to set the scene, I now feel that simply adding sky-coloured tiles is not enough. I may need to make a proper landscape backdrop, for no other reason than to give the level some place. Not entirely sure how I&amp;rsquo;m going to do that.&lt;/p&gt;
&lt;p&gt;Anyway, that&amp;rsquo;s for later. For now, I had another go at working on world 2. Level 2-1 is still barely started: I haven&amp;rsquo;t returned to it yet, and probably won&amp;rsquo;t until I have an idea of how to put that level together. I do have some ideas for level 2-2 and 2-3 and to ease myself in working on them, I started the week by simply building game elements, like a thirst mechanic (world 2 is a desert, as pre-ordained by &lt;a href=&#34;https://youtu.be/Gvr0qU1kPng?si=k9l60HCw9JMXkVyB&#34;&gt;Yahtzee Croshaw&lt;/a&gt;). More on those in the future I&amp;rsquo;m sure, but it was enough to get me to start work on level 2-3, which I&amp;rsquo;m thinking might be set in an underground pyramid with some windy maze elements (original, I know). Not too many windy maze elements, mind you: I generally don&amp;rsquo;t like playing maze levels. But I think a little would add some variety to what I&amp;rsquo;d imagine would be simply jumping puzzles until then. So far this has meant reverting to &amp;ldquo;playing the hits&amp;rdquo; as it were, by starting off with adding a simple lock-and-key hunt, albeit with gems (shout-out to my fellow Keen 4-6 fans). I do have a twist in mind which I hope to slowly introduce to the play as they progress through the level.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll just finish this update off with some screenshots of how level 2-3 looks now in it&amp;rsquo;s super early state.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250726-154418.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated platformer video game features a character navigating through stone and brick structures with a goal indicator at the top left.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250726-154429.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated video game scene features a knight in a maze-like structure with brick walls and wooden crates, aiming to collect a purple gem.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250726-154444.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixel art video game scene shows a knight character on a platform surrounded by brick-like structures and a red arrow above.&#34;&gt;
&lt;p&gt;So that&amp;rsquo;s where things currently stand.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/07/23/since-im-not-a-huge.html</link>
      <pubDate>Wed, 23 Jul 2025 09:02:22 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/23/since-im-not-a-huge.html</guid>
      <description>&lt;p&gt;Since I&amp;rsquo;m not a huge user of iCloud, the fact that I only have 5 GB doesn&amp;rsquo;t offend me. But seeing the breakdown of my storage usage, I can understand why people think Apple is being unreasonable here. To use up 80% of the available storage and leave a measly 1 GB for personal use is not great.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250723-075914.png&#34; alt=&#34;Auto-generated description: A summary of iCloud storage usage shows 4.4 GB used out of 5 GB, with details on backup, photos, and documents, and an upgrade option is offered.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/07/23/logged-into-icloud-for-the.html</link>
      <pubDate>Wed, 23 Jul 2025 08:55:14 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/23/logged-into-icloud-for-the.html</guid>
      <description>&lt;p&gt;Logged into the iCloud web-portal for the first time today (I completely forgot that there was a web portal). I don&amp;rsquo;t know if this is a thing many people use, but it&amp;rsquo;s… okay. A little slow, but potentially useful for those times when you can&amp;rsquo;t access something via native apps.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250723-080934.png&#34; width=&#34;600&#34; height=&#34;406&#34; alt=&#34;Auto-generated description: A digital dashboard displays sections for Photos, Reminders, and Mail, along with a user profile labeled Leon.&#34;&gt;
&lt;p&gt;There is also a sense that it&amp;rsquo;s a little flimsy, that if you push too hard it will fall over. Can&amp;rsquo;t quite put my finger on why that is, but I think it&amp;rsquo;s all the small, thin fonts and pale colours. Just feels a little fragile.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/07/21/kind-of-wished-mailapp-kept.html</link>
      <pubDate>Mon, 21 Jul 2025 09:50:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/21/kind-of-wished-mailapp-kept.html</guid>
      <description>&lt;p&gt;Kind of wished Mail.app kept your email rules when you &lt;a href=&#34;https://discussions.apple.com/thread/255864543&#34;&gt;choose to sign out of iCloud&lt;/a&gt;. I did just that on my work laptop, and today I found all my rules got clobbered. Good thing I &lt;a href=&#34;https://lmika.org/2025/03/17/after-the-last-peanut-incident.html&#34;&gt;documented at least one of them&lt;/a&gt;, so I didn&amp;rsquo;t loose too much. But it&amp;rsquo;s still data loss, and I rather it didn&amp;rsquo;t happen.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250721-084019.png&#34; width=&#34;600&#34; height=&#34;404&#34; alt=&#34;Auto-generated description: A computer interface displays an email rules management window, showing a single rule titled News From Apple.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools — Category Fixer</title>
      <link>https://lmika.org/2025/07/20/devlog-blogging-tools-category-fixer.html</link>
      <pubDate>Mon, 21 Jul 2025 00:09:07 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/07/20/devlog-blogging-tools-category-fixer.html</guid>
      <description>&lt;p&gt;I was going to say that this one was pretty quick to implement, but thinking of it now, it took several hours all up, so maybe it wasn&amp;rsquo;t that quick. It&amp;rsquo;s a feature I&amp;rsquo;ve creatively called the &amp;ldquo;category fixer&amp;rdquo;. Yeah, the name&amp;rsquo;s not great, but it&amp;rsquo;s been a feature I&amp;rsquo;ve been wishing for a while, and it&amp;rsquo;s nice to see it added to Blogging Tools.&lt;/p&gt;
&lt;p&gt;I post blog posts of images which could either be a photo or a screenshot, along with the occasional meme. I have categories for all three, and when I create a new post with an image, I try to make sure the appropriate category is set. Only issue is that I usually forget, and most image posts remain without any category at all. Micro.blog does has a feature of automatically adding posts with image tags into a photos category, but I&amp;rsquo;ve turned that off as I don&amp;rsquo;t like the idea of screenshots going there.&lt;/p&gt;
&lt;p&gt;So I needed a way to fix this, and that&amp;rsquo;s what this new feature does. I&amp;rsquo;ve added a RSS feed parser which will poll my blog&amp;rsquo;s RSS feed for any new or updated posts. If there are any, the HTML content will get parsed to find at least one &lt;code&gt;img&lt;/code&gt;. If it does, an in-app notification will be added. I can then go in and triage these posts, selecting the category they should be sorted into.&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250720-230148.png&#34;
     
        alt=&#34;Auto-generated description: A webpage titled Blogging Tools features notices for posts without categories, each containing expandable text and links for photos, screenshots, and memes.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The in-app notifications, accessible from a new top-level menu item.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250720-230403.png&#34;
     
        alt=&#34;Auto-generated description: A modern building with red and black exterior illuminated at night, featuring large arched windows displaying the word COBURG.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Tapping the disclosure icon reveals the first image found, along with a link to view the entire post.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250720-230424.png&#34;
     
        alt=&#34;Auto-generated description: A magpie is perched on a wooden fence beside a grassy area with trees in the background.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Selecting an option will update the notice then and there (using HTMX). The notice isn&amp;#39;t cleared until the category change has been made, which does require a page refresh.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;About half the work was building out these in-app notifications. They&amp;rsquo;re all stored in a &lt;code&gt;sqlite3&lt;/code&gt; table and mainly consist of a notification type, brief message, and an extendable set of properties which are stored as a JSON object.&lt;/p&gt;
&lt;p&gt;For the feed parser, I&amp;rsquo;m using &lt;a href=&#34;https://github.com/mmcdole/gofeed/&#34;&gt;gofeed&lt;/a&gt;, the same library I&amp;rsquo;m using for reading podcast feeds. I can definitely recommend this library for parsing RSS feeds if you&amp;rsquo;re using Golang. There was a little bit I had to add around this library to detect when feed items have previously been read or updated. The way I&amp;rsquo;m detecting this is by recording the GUID, along with a SHA-1 hash of the title, description, and content in the database.  Parsing the feed HTML is done using &lt;a href=&#34;https://github.com/PuerkitoBio/goquery&#34;&gt;goquery&lt;/a&gt;, allowing me to easily select the first &lt;code&gt;img&lt;/code&gt; tag of a HTML fragment. For fetching and setting the categories, I&amp;rsquo;m using a home grown Micropub client.&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s &amp;ldquo;category fixer&amp;rdquo;. A lot of infrastructure was added for this one which could be useful in the future. I do have some other ideas that would follow this sort of pattern: a new post is picked up by the RSS reader, and an in-app notification is raised for me to action. I suspect this may becoming a bit of a pattern.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Brief Look At Microsoft&#39;s New CLI Text Editor</title>
      <link>https://lmika.org/2025/06/23/took-a-look-at-microsofts.html</link>
      <pubDate>Mon, 23 Jun 2025 09:09:38 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/06/23/took-a-look-at-microsofts.html</guid>
      <description>&lt;p&gt;Took a look at &lt;a href=&#34;https://devblogs.microsoft.com/commandline/edit-is-now-open-source/&#34;&gt;Microsoft&amp;rsquo;s new CLI text editor&lt;/a&gt;. There&amp;rsquo;s no MacOS version yet but &lt;a href=&#34;https://simonwillison.net/2025/Jun/21/edit-is-now-open-source/#atom-everything&#34;&gt;Simon Willison&lt;/a&gt; has made a Docker image for it, and once I satisfied MacOS&amp;rsquo; insatiable fear that I don&amp;rsquo;t know what I&amp;rsquo;m doing regarding Terminal and Docker&amp;rsquo;s access to other files&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, I managed to launch it.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250623-075947.png&#34; width=&#34;600&#34; height=&#34;395&#34; alt=&#34;Auto-generated description: A text editor window displays a simple script mentioning Microsoft&#39;s CLI text editor, with menus for File, Edit, View, and Help visible at the top.&#34;&gt;
&lt;p&gt;And yeah, works well. Reminds me of &lt;code&gt;EDIT.EXE&lt;/code&gt; from my DOS using days.&lt;/p&gt;
&lt;p&gt;Since I was running this in Docker, I knew my experience may be slightly off from what&amp;rsquo;s expected. But one thing I would suggest Microsoft doing if they do want to bring this to the Mac is to have a version that works with terminals with light backgrounds: the grey body text is readable but a bit more contrast would be welcome. Also, adding some Emacs/Readline keyboard bindings would be nice. I found myself pressing Option+Left, which I&amp;rsquo;ve mapped to Ctrl+W, to try and go back a word, and I kept getting asked if I wanted to close and save the file. Seems like the keyboard bindings are mapped to what is typical on Windows, which I can understand.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250623-080114.png&#34; width=&#34;600&#34; height=&#34;787&#34; alt=&#34;Auto-generated description: Two text editor windows display partial menus including file options like New File, with some text in the lower window reading Hello.&#34;&gt;
&lt;figcaption&gt;I think the colours shown on the menu are little buggy.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But on the whole, I can see this being useful for Windows users that need to duck into a text file when they&amp;rsquo;re using the CLI. I don&amp;rsquo;t think it&amp;rsquo;ll be useful to me — I do know how to quit from Vim — but I frequently need to do this myself whenever I&amp;rsquo;m using the Terminal, and having to switch modes from CLI to GUI always includes a risk of loosing context. So I could imagine appreciating this if I were a Windows users.&lt;/p&gt;
&lt;p&gt;One last thing: I like how they managed to get it down to less than 250kB. A worthy goal for any editor that is not aiming to be feature rich enough to replace your daily driver.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Four prompts in total today. Four!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/05/16/my-first-automation-to-assist.html</link>
      <pubDate>Fri, 16 May 2025 14:47:47 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/05/16/my-first-automation-to-assist.html</guid>
      <description>&lt;p&gt;My first automation to assist me with this &amp;ldquo;issue driven development&amp;rdquo; approach: a Keyboard Maestro macro which will activate Obsidian, go to the end of the document, and add a new line beginning with the current time.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250516-134001.png&#34; width=&#34;600&#34; height=&#34;572&#34; alt=&#34;Auto-generated description: A configuration window for creating a new timestamped line in Obsidian, detailing trigger options and actions.&#34;&gt;
&lt;p&gt;My goal is to have one Obsidian note per Jira task, which I will have open when I&amp;rsquo;m actively working on it. When I want to record something, like a decision or passing thought, I&amp;rsquo;ll press Cmd+Option+Ctrl+L to fire this macro, and start typing. Couldn&amp;rsquo;t resist adding some form of automation for this, but hey: at least it&amp;rsquo;s not some &lt;a href=&#34;https://lmika.org/2023/07/17/lava-stream.html&#34;&gt;hacked-up, makeshift app&lt;/a&gt; this time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Blogging Tools — Ideas For Stills For A Podcast Clips Feature</title>
      <link>https://lmika.org/2025/05/07/devlog-blogging-tools-podcast-clips.html</link>
      <pubDate>Thu, 08 May 2025 00:18:48 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/05/07/devlog-blogging-tools-podcast-clips.html</guid>
      <description>&lt;p&gt;I recently discovered that Pocketcasts for Android have changed their clip feature. It still exists, but instead of producing a video which you could share on the socials, it produces a link to play the clip from the Pocketcasts web player. Understandable to some degree: it always took a little bit of time to make these videos. But hardly a suitable solution for sharing clips of private podcasts: one could just listen to the entire episode from the site. Not to mention relying on a dependent service for as long as those links (or the original podcast) is around.&lt;/p&gt;
&lt;p&gt;So… um, yeah, I&amp;rsquo;m wondering if I could building something for myself that could replicate this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m thinking of another module for Blogging Tools. I was already using this tool to &lt;a href=&#34;https://lmika.org/2024/11/11/oof-finally-fixed.html&#34;&gt;crop the clip videos&lt;/a&gt; that came from Pocketcasts so it was already in my workflow. It also has &lt;code&gt;ffmpeg&lt;/code&gt; bundled in the deployable artefact, meaning that I could use to produce video. Nothing fancy: I&amp;rsquo;m thinking of a still showing the show title, episode title, and artwork, with the audio track playing. I pretty confident that &lt;code&gt;ffmpeg&lt;/code&gt; can handle such tasks.&lt;/p&gt;
&lt;p&gt;I decided to start with the fun part: making the stills. I started with using &lt;a href=&#34;https://github.com/llgcode/draw2d&#34;&gt;Draw2D&lt;/a&gt; to provide a very simple frame where I could place the artwork and render the text. I just started with primary colours so I could get the layout looking good:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/podcast-background-1.png&#34; width=&#34;600&#34; height=&#34;338&#34; class=&#34;block-center&#34; alt=&#34;Auto-generated description: A date, episode title, and show name are displayed alongside an image of ocean waves against rocks in a colorful border.&#34;&gt;
&lt;p&gt;I&amp;rsquo;m using Roboto Semi-bold for the title font, and Oswald Regular for the date. I do like the look of Oswald, the narrower style contrasts nicely with the neutral Roboto. Draw2D provides methods for measuring text sizes, which I&amp;rsquo;m using to power the text wrapping layout algorithm (it&amp;rsquo;s pretty dumb. It basically adds words to a line until it can&amp;rsquo;t fit the available space)&lt;/p&gt;
&lt;p&gt;The layout I got nailed down yesterday evening. This evening I focused on colour.&lt;/p&gt;
&lt;p&gt;I want the frame to be interesting and close to the prominent colours that come from the artwork. I found &lt;a href=&#34;https://github.com/EdlinOrg/prominentcolor&#34;&gt;this library&lt;/a&gt; which returns the dominant colours of an image using K-means clustering. I&amp;rsquo;ll be honest: I haven&amp;rsquo;t looked at how this actually works. But I tried the library out with some random artwork from &lt;a href=&#34;https://picsum.photos&#34;&gt;Lorem Picsum&lt;/a&gt;, and I was quite happy with the colours it was returning. After adding &lt;a href=&#34;https://github.com/teacat/noire&#34;&gt;this library&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; to calculate the contract for the text colour, plus a slight shadow, and the stills started looking pretty good:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250507-213441.png&#34; width=&#34;600&#34; height=&#34;517&#34; alt=&#34;Auto-generated description: Six rectangular cards each feature a different background image with the date 14 April 2020, text A pretty long episode title, and My test show.&#34;&gt;
&lt;p&gt;I then tried some real podcast artwork, starting with &lt;a href=&#34;https://atp.fm&#34;&gt;ATP&lt;/a&gt;. And that&amp;rsquo;s where things started going off the rails a little:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250507-224100.png&#34; width=&#34;600&#34; height=&#34;342&#34; alt=&#34;Auto-generated description: Four color variations of a promotional card design featuring a logo with rainbow stripes, a date of 14 April 2020, and text stating A pretty long episode title and My test show.&#34;&gt;
&lt;p&gt;The library returns the colours in order of frequency, and I was using the first colour as the border and the second as the card background. But I&amp;rsquo;m guessing since the ATP logo has so few actual colour, the K-means algorithm was finding those of equal prominence and returning them in a random order. Since the first and second are of equal prominence, the results were a little garish and completely random.&lt;/p&gt;
&lt;p&gt;To reduce the effects of this, I finished the evening by trying a variation where the card background was simply a shade of the border. That still produced random results, but at least the colour choices were a little more harmonious:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250507-224112.png&#34; width=&#34;600&#34; height=&#34;342&#34; alt=&#34;Auto-generated description: A series of four visually distinct cards display a logo, date, episode title, and show subtitle, each set against different colored backgrounds.&#34;&gt;
&lt;p&gt;I&amp;rsquo;m not sure what I want to do here. I&amp;rsquo;ll need to explore the library a little, just to see whether it&amp;rsquo;s possible to reduce the amount of randomness. Might be that I go with the shaded approach and just keep it random: having some variety could make things interesting.&lt;/p&gt;
&lt;p&gt;Of course, I&amp;rsquo;m still doing the easy and fun part. How the UI for making the clip will look is going to be a challenge. More on that in the future if I decide to keep working on this. And if not, at least I&amp;rsquo;ve got these nice looking stills.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;The annoying thing about this library is that it doesn&amp;rsquo;t use Go&amp;rsquo;s standard &lt;code&gt;Color&lt;/code&gt; type, nor does it describe the limits of each component. So for anyone using this library: the range for R, G, and B go from 0 to 255, and A goes from 0 to 1.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Devlog: Godot Game Update</title>
      <link>https://lmika.org/2025/04/27/a-brief-status-update-on.html</link>
      <pubDate>Sun, 27 Apr 2025 12:29:47 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/04/27/a-brief-status-update-on.html</guid>
      <description>&lt;p&gt;A brief status update on that Godot game. I think we&amp;rsquo;re pretty close to a finished 4-1 level. The underground section has been built, and the level has been decorated. I&amp;rsquo;ve also added a couple of secrets, which needed a few new mechanics — like doorways, which are used to transport the player around the level — plus some refinement to existing ones. I am a little concerned about the amount of waiting involved near the end of the first half, where the player will need to make their way across a large gap by jumping on the slow cycling &amp;ldquo;layer 2&amp;rdquo; tile layer. I&amp;rsquo;ll see what feedback I get from play-testers about this.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250427-111949.png&#34; width=&#34;600&#34; height=&#34;362&#34; alt=&#34;Auto-generated description: A pixelated, side-scrolling platformer video game scene features a knight character on a cliff with blocks, coins, and a treasure chest in view.&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve also filled in the backdrop of all the levels to date. The grey default background colour has been replaced with a sky made of colour bands that came with the asset set I&amp;rsquo;m using. It&amp;rsquo;s basic, maybe too basic, but as long as it doesn&amp;rsquo;t clash with the interactive elements, and in the interest of &amp;ldquo;merely shipping something,&amp;rdquo; I think it&amp;rsquo;ll do for now.&lt;/p&gt;
&lt;p&gt;Finally, I made some changes from feedback I got from play-testers. Movement can now be made using WASD, along with the inverted-T arrow keys. I&amp;rsquo;ve also bound jump to the Enter key for those that want to use WASD. I made some changes in how secrets are telegraphed with the goal of making them more consistent. This is always a bit of a balancing act, as I don&amp;rsquo;t want to make them too obvious: they&amp;rsquo;re secrets after all. I added a tiny bit of &amp;ldquo;slip&amp;rdquo; to the player&amp;rsquo;s movement, so they don&amp;rsquo;t stop dead whenever the key is released. I didn&amp;rsquo;t want to go too far here: adding too much inertia introduced the risk of the player just falling off a platform after landing from a jump, which I don&amp;rsquo;t think is a great experience. And I fixed a window resizing bug (Godot enables window resizing by default, for anyone else who needs to know this).&lt;/p&gt;
&lt;p&gt;I think the next thing to do is to organise another version for play-testers, just to gauge what they think of level 4-1. Then it&amp;rsquo;s either on to level 4-2, or even introduce a new level between 1-1 and 1-2 to reduce the ramp up in difficulty between the two levels. Fortunately I&amp;rsquo;ve got some ideas on what I want to do for both of theses, so they&amp;rsquo;ll be easy starters. I&amp;rsquo;ve still got nothing for level 2-1, but I have a few ideas on what I want to do for 2-2, so maybe I&amp;rsquo;ll start that too.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/04/11/does-vivaldi-mobile-for-android.html</link>
      <pubDate>Fri, 11 Apr 2025 14:03:15 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/04/11/does-vivaldi-mobile-for-android.html</guid>
      <description>&lt;p&gt;Does Vivaldi Mobile for Android have GIF support? Yes… I guess? Not entirely sure who&amp;rsquo;s asking. Or why. Are these the same people who want to know if Vivaldi has paid stickers or file sharing support? Is this for a hypothetical messaging app?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250411-125858.png&#34; width=&#34;600&#34; height=&#34;750&#34; alt=&#34;Auto-generated description: A review screen for the Vivaldi Browser app asks the user to rate the app with stars and provide feedback, with additional questions about GIF support.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/04/08/blessed-be-the-mailapp-view.html</link>
      <pubDate>Tue, 08 Apr 2025 11:01:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/04/08/blessed-be-the-mailapp-view.html</guid>
      <description>&lt;p&gt;Blessed be the Mail.app View menu and the option to hide the useless Apple AI priority messages. My Inbox is now slightly more sane.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250408-095840.png&#34; width=&#34;600&#34; height=&#34;389&#34; alt=&#34;Auto-generated description: A computer screen displays a dropdown menu with various options such as Show Tab Bar and Show Priority, against a background of tall trees.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/03/29/a-bit-more-on-the.html</link>
      <pubDate>Sat, 29 Mar 2025 11:36:52 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/29/a-bit-more-on-the.html</guid>
      <description>&lt;p&gt;A bit more on the Godot game this morning, this time working on background tiles artwork. Made some grey masonry tiles for the end castle sequences. Also tried some background rocks for underground areas. I&amp;rsquo;m pretty rubbish at anything organic, but they didn&amp;rsquo;t turn out too bad.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250329-113300.png&#34; width=&#34;600&#34; height=&#34;564&#34; alt=&#34;Auto-generated description: Two rectangular pixel art frames with stone textures, one in brown and the other in gray, are displayed with matching filled versions inside them.&#34;&gt;
&lt;figcaption&gt;Right side has the background tiles surrounded with their complementary foreground tiles on the left.&lt;/figcaption&gt;
&lt;/figure&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/03/29/it-pains-me-that-forgejos.html</link>
      <pubDate>Sat, 29 Mar 2025 11:23:34 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/29/it-pains-me-that-forgejos.html</guid>
      <description>&lt;p&gt;It pains me that Forgejo&amp;rsquo;s CI &amp;ldquo;pipeline running&amp;rdquo; animation spins anti-clockwise, as if you&amp;rsquo;re going backwards in time. A metaphor, perhaps? &lt;em&gt;Services get undeployed, binaries go back to the source code, projects return to their seeds of ideas.&lt;/em&gt; 🤔💭&lt;/p&gt;
&lt;p&gt;Oh, build&amp;rsquo;s done. Never-mind. 😀&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/cleanshot-2025-03-29-at-11.17.06.gif&#34; width=&#34;344&#34; height=&#34;80&#34; alt=&#34;A commit titled &#39;Added the grid image processor&#39; by user &#39;lmika&#39; was pushed to the main branch with the workflow file &#39;deploy.yaml&#39;, with a yellow spinner spinning in an anti-clockwise direction.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/03/10/was-looking-at-how-i.html</link>
      <pubDate>Mon, 10 Mar 2025 21:29:31 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/10/was-looking-at-how-i.html</guid>
      <description>&lt;p&gt;Was looking at how I could add hazards to my Godot project, such as spikes. My first idea was to find a way to detect collisions with tiles in a &lt;code&gt;TileMap&lt;/code&gt; in Godot. But there was no real obvious way to do so, suggesting to me that this was not the way I should be going about this. Many suggested simply using an &lt;code&gt;Area2D&lt;/code&gt; node to detect when a play touches a hazard.&lt;/p&gt;
&lt;p&gt;I was hesitant to copy and paste the scene node I had which handled the collision signal and kill the player — the so-called &amp;ldquo;kill zone&amp;rdquo; scene —but today I learnt that it&amp;rsquo;s possible to add multiple &lt;code&gt;CollisionShape2D&lt;/code&gt; nodes to an &lt;code&gt;Area2D&lt;/code&gt; node. This meant I needed only a single &amp;ldquo;kill zone&amp;rdquo; scene node, and just draw out the kill zones over the spikes as children. The TileMap simply provides the graphics.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250310-211225.png&#34; width=&#34;600&#34; height=&#34;427&#34; alt=&#34;Auto-generated description: A game development interface displaying level design with a grid layout, tiles, and collision shapes is shown.&#34;&gt;
&lt;p&gt;This discovery may seem a little trivial, but I&amp;rsquo;d prefer to duplicate as few nodes as a can, just so I&amp;rsquo;ve got less to touch when I want to change something.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/03/10/turns-out-my-previous-solution.html</link>
      <pubDate>Mon, 10 Mar 2025 09:49:38 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/10/turns-out-my-previous-solution.html</guid>
      <description>&lt;p&gt;Tried opening my Godot project this morning and was greeted with the following error:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250310-094231.png&#34; width=&#34;600&#34; height=&#34;176&#34; alt=&#34;Auto-generated description: A computer screen displays a development environment with an error message pop-up about an invalid or missing scene file.&#34;&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;scene/resources/resource_format_text.cpp:284 - res://scenes/falling_platform.tscn:14 - Parse Error: 
Failed loading resource: res://scenes/falling_platform.tscn. Make sure resources have been imported by opening the project in the editor at least once.
Failed to instantiate scene state of &amp;#34;res://scenes/falling_platform.tscn&amp;#34;, node count is 0. Make sure the PackedScene resource is valid.
Failed to load scene dependency: &amp;#34;res://scenes/falling_platform.tscn&amp;#34;. Make sure the required scene is valid.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Traced it back to the &lt;a href=&#34;https://lmika.org/2025/03/08/adventures-in-godot-respawning-a.html&#34;&gt;technique I was using to respawn the falling platform&lt;/a&gt;. Looks like Godot didn&amp;rsquo;t like the two preloads I included:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# file: scripts/falling_platform.gd

@onready var FallingPlatform = preload(&amp;#34;res://scenes/falling_platform.tscn&amp;#34;)
@onready var FallingPlatformScript = preload(&amp;#34;res://scripts/falling_platform.gd&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This resulted in a parse error and Godot thinking the level scene was corrupted.  In retrospect, this kinda makes sense. What I doing was technically a circular dependency, where the scene and script was trying to preload itself. I was hoping Godot was smart enough to recognise this, but I guess not.&lt;/p&gt;
&lt;p&gt;So I had to change the respawn code. I modified it to make use the &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-method-duplicate&#34;&gt;duplicate&lt;/a&gt; method. Here&amp;rsquo;s the revised version:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;func respawn():	
    var dup = self.duplicate()
    dup.global_position = init_global_position	

    # Duplicate also copies the velocity so zero it out here
    dup.velocity = Vector2(0, 0)
    get_tree().current_scene.add_child(dup)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After some limited testing, this seems to work. One good thing about this approach is that it looks like duplicate copies the script, so I no longer need to do anything special here.&lt;/p&gt;
&lt;p&gt;So I guess the lesson here is don&amp;rsquo;t try to preload the script within itself.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Adventures In Godot: Respawning A Falling Platform</title>
      <link>https://lmika.org/2025/03/08/adventures-in-godot-respawning-a.html</link>
      <pubDate>Sat, 08 Mar 2025 12:35:07 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/08/adventures-in-godot-respawning-a.html</guid>
      <description>&lt;p&gt;My taste of going through a Godot tutorial last week has got me wanting more, so I&amp;rsquo;ve set about building a game with it. Thanks to my limited art skills, I&amp;rsquo;m using &lt;a href=&#34;https://brackeysgames.itch.io/brackeys-platformer-bundle&#34;&gt;the same asset pack&lt;/a&gt; that was used in the video, although I am planning to add a bit of my own here and there.&lt;/p&gt;
&lt;p&gt;But it&amp;rsquo;s the new mechanics I enjoy working on, such as adding falling platforms. If you&amp;rsquo;ve played any platformer, you know what these look like: platforms that are suspended in air, until the player lands on them, at which point gravity takes a hold and they start falling, usually into a pit killing the player in the process:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://media.lmika.org/videos/2025/falling-platform-1.mp4&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The platforms are built as a &lt;code&gt;CharacterBody2D&lt;/code&gt; with an &lt;code&gt;Area2D&lt;/code&gt; that will detect when a player enters the collision shape. When they do, a script will run which will have the platform &amp;ldquo;slipping&amp;rdquo; for a second, before the gravity is turned on and the platform falls under it&amp;rsquo;s own weight. The whole thing is bundled as a reusable scene which I could drag into the level I&amp;rsquo;m working on.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250308-123202.png&#34; width=&#34;600&#34; height=&#34;391&#34; alt=&#34;Auto-generated description: A game development interface with a sprite and code editor is shown, from a software environment like Godot.&#34;&gt;
&lt;p&gt;I got the basics of this working reasonably quickly, yet today, I had a devil of a time going beyond that.
The issue was that I wanted the platform to respawn after it fell off the map, so that the player wouldn&amp;rsquo;t get soft-locked at at an area where the platform was needed to escape. After a false-start trying to reposition the platform at it&amp;rsquo;s starting point after it fell, I figured it was just easier to respawn the platform when the old one was removed. To do this I had to solve two problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How do I get the platform starting point?&lt;/li&gt;
&lt;li&gt;How can I actually respawn the platform?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;getting-the-starting-point&#34;&gt;Getting The Starting Point&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;Node2D&lt;/code&gt; object has the property &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_node2d.html#class-node2d-property-global-position&#34;&gt;global_position&lt;/a&gt;, which returns the position of the object based on the world coordinates. However, it seems like this position is not correct when the &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-private-method-init&#34;&gt;_init&lt;/a&gt; function of the attached script is called. I suspect this is because this function is called before the platform is added to the scene tree, when the final world coordinates are known.&lt;/p&gt;
&lt;p&gt;Fortunately, there exists the &lt;code&gt;_ready&lt;/code&gt; notification, which is invoked when the node is added to the scene tree. After some experimentation, I managed to confirm that &lt;code&gt;global_position&lt;/code&gt; properly was correct. So tracking the starting point is a simple as storing that value in a variable:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;var init_global_position = null

func _ready():
	init_global_position = global_position
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another option is to use the &lt;code&gt;_enter_tree()&lt;/code&gt; notification. From &lt;a href=&#34;https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_notifications.html#ready-vs-enter-tree-vs-notification-parented&#34;&gt;the documentation&lt;/a&gt;, it looks like either would probably work here, with the only difference being the order in which this notification is invoked on parents and children (&lt;code&gt;_enter_tree&lt;/code&gt; is called by the parent first, whereas &lt;code&gt;_ready&lt;/code&gt; is called by the children first).&lt;/p&gt;
&lt;h2 id=&#34;respawning-the-platform&#34;&gt;Respawning The Platform&lt;/h2&gt;
&lt;p&gt;The next trick was finding out how to respawn the platform. The usual technique for doing so, based on the results of my web searching, is to load the platform scene, &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_packedscene.html#description&#34;&gt;instantiate a new instance&lt;/a&gt; of it, and added it to the scene tree.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@onready var FallingPlatform = preload(&amp;#34;res://scenes/falling_platform.tscn&amp;#34;)

func respawn():	
    var dup = FallingPlatform.instantiate()
    add_child(dup)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Many of the examples I&amp;rsquo;ve seen online added the new scene node as a child of the current node. This wouldn&amp;rsquo;t work for me as I wanted to free the current node at the same time, and doing so would free the newly instantiated child. The fix for this was easy enough: I just added the new node as a child of the current scene.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@onready var FallingPlatform = preload(&amp;#34;res://scenes/falling_platform.tscn&amp;#34;)


func respawn():	
    var dup = FallingPlatform.instantiate()
    get_tree().current_scene.add_child(dup)
    queue_free()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I still had to reposition the new node to the spawn point. Fortunately the &lt;code&gt;global_position&lt;/code&gt; property is also settable, so it was simply a matter of setting that property before adding it to the tree (this is so that it&amp;rsquo;s correct when the newly instantiated node receives the &lt;code&gt;_ready&lt;/code&gt; notification).&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@onready var FallingPlatform = preload(&amp;#34;res://scenes/falling_platform.tscn&amp;#34;)

func respawn():	
    var dup = FallingPlatform.instantiate()
    dup.global_position = init_global_position
    get_tree().current_scene.add_child(dup)
    queue_free()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This spawned the platform at the desired positioned, but there was a huge problem: when the player jumped on the newly spawn platform, it wouldn&amp;rsquo;t fall. The &lt;code&gt;Area2D&lt;/code&gt; connection was not invoking the script to turn on the gravity:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://media.lmika.org/videos/2025/falling-platform-2.mp4&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;It took me a while to figured out what was going on, but I came to the conclusion that the packed scene was loading properly, but without the script attached. Turns out a &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_script.html&#34;&gt;Script&lt;/a&gt; is a resource separate from the scene, and can be loaded and attached to an object via the &lt;a href=&#34;https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-set-script&#34;&gt;set_script&lt;/a&gt; method:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@onready var FallingPlatform = preload(&amp;#34;res://scenes/falling_platform.tscn&amp;#34;)
@onready var FallingPlatformScript = preload(&amp;#34;res://scripts/falling_platform.gd&amp;#34;)

func respawn():	
    var dup = FallingPlatform.instantiate()
    dup.set_script(FallingPlatformScript)

    dup.global_position = init_global_position
    get_tree().current_scene.add_child(dup)
    queue_free()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, after figuring all this out, I was able to spawn a new falling platform, have it positioned at the starting position of the old platform, and react to the player standing on it.&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://media.lmika.org/videos/2025/falling-platform-3.mp4&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The time it took to work this out is actually a little surprising. I was expecting others to run into the same problem I was facing, where they were trying to instantiate a scene only to have the scripts not do anything. Yet it took me 45 minutes of web searching through Stack Overflow and forum posts that didn&amp;rsquo;t solve my problem. It was only after a bit of experimentation and print-debugging on my own that I realised that I actually had to attached the script after instantiating the node.&lt;/p&gt;
&lt;p&gt;To be fair, I will attribute some of this to not understanding the problem at first: I actually thought the &lt;code&gt;Area2D&lt;/code&gt; wasn&amp;rsquo;t actually being instantiated at all. Yet not one of the Stack Overflow answers or forum post floated the possibility that the script wasn&amp;rsquo;t being loaded alongside the scene.  This does suggest to me that my approach may not be optimal. There does exist a &amp;ldquo;Local to Scene&amp;rdquo; switch in the script inspector that could help, although turning it on doesn&amp;rsquo;t seem to do much. But surely there must be some way to instantiate the script alongside the scene.&lt;/p&gt;
&lt;p&gt;Anyway, that&amp;rsquo;s for later. For now, I&amp;rsquo;m happy that I&amp;rsquo;ve got something that works.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/03/06/cathoderaytube-yes.html</link>
      <pubDate>Thu, 06 Mar 2025 15:49:37 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/06/cathoderaytube-yes.html</guid>
      <description>&lt;p&gt;🔗 &lt;a href=&#34;https://cathoderay.tube&#34;&gt;cathoderay.tube&lt;/a&gt;&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250306-154702.png&#34; width=&#34;600&#34; height=&#34;443&#34; alt=&#34;Auto-generated description: A web browser window displays the word computers!!! in large text on a plain white background.&#34;&gt;
&lt;p&gt;Yes.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Running PeerTube In Coolify</title>
      <link>https://lmika.org/2025/03/01/running-peertube-in-coolify.html</link>
      <pubDate>Sat, 01 Mar 2025 15:39:21 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/03/01/running-peertube-in-coolify.html</guid>
      <description>&lt;p&gt;A few months ago, I moved my PeerTube instance over to Coolify, so that I could shutdown the Linux instance it was running on. I had a PeerTube instance for a little over a year, although I hardly posted anything on there and it&amp;rsquo;s never seen much traffic. I did want to keep it around though, as I had a few video embeds scattered around the internet. It just didn&amp;rsquo;t need to be on it&amp;rsquo;s own server.&lt;/p&gt;
&lt;p&gt;This post is not about how I ported this instance over to Coolify. It&amp;rsquo;s been a few months, and several hours of trial and error to get that working. But I had been asked how one could setup their own PeerTube instance in Coolify, and I wanted a documented approach for how one could do so, should there be a need to spin up a new instance from scratch. PeerTube is not one of the builtin services that Coolify offers, at least at the time of this post, so a manual process to setting this up is required.&lt;/p&gt;
&lt;p&gt;A few notes about the PeerTube instance you will have at the end of this process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The database and videos will be stored on the host&amp;rsquo;s file system. This should keep them persistent over container redeployments but will mean you&amp;rsquo;ll need to make sure the host&amp;rsquo;s file system is backed up somehow. Most VPS&amp;rsquo;s have a backup option, and PeerTube does offer a way to upload videos to an object store, although I&amp;rsquo;m not sure how to set that up.&lt;/li&gt;
&lt;li&gt;Email sending will be disabled. This works for me as I&amp;rsquo;m hosting these videos for myself, and I didn&amp;rsquo;t want to go through the trouble of deploying Postfix or paying for a hosted SMTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note also that this is not the approach I took to setup my instance, so some things may not be correct or may require further efforts to get working properly. Consider this as more of a &amp;ldquo;getting starting&amp;rdquo; guide, rather a comprehensive go-to-production process.&lt;/p&gt;
&lt;h2 id=&#34;prerequisite&#34;&gt;Prerequisite&lt;/h2&gt;
&lt;p&gt;You will need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.coolify.io&#34;&gt;Coolify&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A suitable Linux host managed by Coolify that meets &lt;a href=&#34;https://joinpeertube.org/faq#should-i-have-a-big-server-to-run-peertube&#34;&gt;PeerTube&amp;rsquo;s minimum specs&lt;/a&gt;. It can be the same host that&amp;rsquo;s running Coolify if necessary, but a separate one is probably worth considering, just so that it doesn&amp;rsquo;t interfere with Coolify itself.&lt;/li&gt;
&lt;li&gt;A domain name to use, for example &amp;ldquo;peertube-demo.lmika.xyz&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;d recommend using &lt;a href=&#34;https://coolify.io/docs/knowledge-base/proxy/caddy/overview&#34;&gt;Caddy as a Coolify proxy&lt;/a&gt; so as to get automatic TLS certificates.&lt;/p&gt;
&lt;h2 id=&#34;docker-compose&#34;&gt;Docker Compose&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll be using a modified version of &lt;a href=&#34;https://docs.joinpeertube.org/install/docker&#34;&gt;PeerTube&amp;rsquo;s Docker Compose&lt;/a&gt; file, with the .env file embedded and the Nginx deployments removed. A copy of the file is provided below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;x-env&lt;/span&gt;: &lt;span style=&#34;color:#75715e&#34;&gt;&amp;amp;env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Database / Postgres service configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;POSTGRES_USER&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;peertube&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;POSTGRES_PASSWORD&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;database-password&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;POSTGRES_DB&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;peertube&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_DB_USERNAME&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;peertube&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_DB_PASSWORD&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;database-password&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_DB_SSL&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_DB_HOSTNAME&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;postgres&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# PeerTube server configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_WEBSERVER_HOSTNAME&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;peertube-domain&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_WEBSERVER_HTTPS&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_WEBSERVER_PORT&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# If you need more than one IP as trust_proxy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# pass them as a comma separated array:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_TRUST_PROXY&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;[&amp;#34;127.0.0.1&amp;#34;, &amp;#34;loopback&amp;#34;, &amp;#34;172.18.0.0/16&amp;#34;]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public-read&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;private&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Generate one using `openssl rand -hex 32`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SECRET&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;random-string&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_LOG_LEVEL=info&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# /!\ Prefer to use the PeerTube admin interface to set the following configurations /!\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_SIGNUP_ENABLED=true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_TRANSCODING_ENABLED=true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_CONTACT_FORM_ENABLED=true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# E-mail configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# If you use a Custom SMTP server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_SMTP_USERNAME:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;#PEERTUBE_SMTP_PASSWORD:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Default to Postfix service name &amp;#34;postfix&amp;#34; in docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# May be the hostname of your Custom SMTP server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SMTP_HOSTNAME&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;postfix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SMTP_PORT&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SMTP_FROM&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;noreply@&amp;lt;peertube-domain&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SMTP_TLS&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_SMTP_DISABLE_STARTTLS&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;PEERTUBE_ADMIN_EMAIL&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;&amp;lt;peertube-admin-address&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;peertube&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;chocobozzz/peertube:production-bookworm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;: &lt;span style=&#34;color:#75715e&#34;&gt;*env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     - &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1935:1935&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     - &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;9000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;./docker-volume/data:/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;postgres&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# - postfix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;restart&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;always&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;postgres&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;postgres:13-alpine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;: &lt;span style=&#34;color:#75715e&#34;&gt;*env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;./docker-volume/db:/var/lib/postgresql/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;restart&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;always&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;redis:6-alpine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ae81ff&#34;&gt;./docker-volume/redis:/data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;restart&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;always&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Uncomment if you want to use postfix (I don&amp;#39;t so I left it disabled)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# postfix:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#   image: mwader/postfix-relay&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#   &amp;lt;&amp;lt;: *env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#   volumes:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#     - ./docker-volume/opendkim/keys:/etc/opendkim/keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#   restart: &amp;#34;always&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make a copy of this file in a text editor and replace the following placeholders:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;database-password&amp;gt;&lt;/code&gt;: Choose a suitable, preferably random, password to use for the database.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;peertube-domain&amp;gt;&lt;/code&gt;: Your domain of choice, e.g. &lt;code&gt;peertube-demo.lmika.xyz&lt;/code&gt;. This should be without the &amp;ldquo;https://&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;random-string&amp;gt;&lt;/code&gt;: Generate a random string by running &lt;code&gt;openssl rand -hex 32&lt;/code&gt; and use that here&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;peertube-admin-address&amp;gt;&lt;/code&gt;: This is unused but should be set to avoid server errors. Set it to something suitable, such as &lt;code&gt;admin@peertube.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;deploying-the-service&#34;&gt;Deploying The Service&lt;/h2&gt;
&lt;p&gt;Log into Coolify, go to &amp;ldquo;Projects&amp;rdquo;, and click &amp;ldquo;Add&amp;rdquo;.  Give your project a name then click &amp;ldquo;Continue&amp;rdquo;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113259.png&#34; width=&#34;600&#34; height=&#34;395&#34; alt=&#34;Auto-generated description: A user interface displays a New Project creation window with fields for name and description, surrounded by navigation options like Projects, Servers, and Settings.&#34;&gt;
&lt;p&gt;Within the project, add a new resource. The resource you&amp;rsquo;d want to choose is &amp;ldquo;Docker Compose Empty&amp;rdquo;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113306.png&#34; width=&#34;600&#34; height=&#34;400&#34; alt=&#34;Auto-generated description: A screenshot of the Coolify application interface displays a menu with various options like Projects, Servers, and Databases, highlighting Docker Compose Empty.&#34;&gt;
&lt;p&gt;Select the server you want to deploy PeerTube to, then copy and paste the compose file (make sure the placeholders are set).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113312.png&#34; width=&#34;600&#34; height=&#34;336&#34; alt=&#34;Auto-generated description: A Docker Compose configuration window is open in a development environment with code centered on PostgreSQL service settings.&#34;&gt;
&lt;p&gt;Before deploying, we&amp;rsquo;ll need to setup the proxy. In the Services list, locate the &amp;ldquo;PeerTube&amp;rdquo; one and click &amp;ldquo;Settings&amp;rdquo;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113319.png&#34; width=&#34;600&#34; height=&#34;336&#34; alt=&#34;Auto-generated description: A configuration dashboard displays sections for service stacks, environment variables, services, networking, deployments, and tasks, with an option to deploy changes.&#34;&gt;
&lt;p&gt;In the &amp;ldquo;Domains&amp;rdquo; text field, enter &lt;code&gt;https://&amp;lt;peertube-domain&amp;gt;:9000&lt;/code&gt; (the port number is used by Coolify to map the service to the proxy, but the service will be listening on port 443).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113327.png&#34; width=&#34;600&#34; height=&#34;336&#34; alt=&#34;Auto-generated description: Configuration settings for a service called PeerTube are displayed, with options to save or delete, and a highlighted domain URL.&#34;&gt;
&lt;p&gt;Click &amp;ldquo;Save&amp;rdquo;, then click &amp;ldquo;Back&amp;rdquo; to go back to the list of services, then click &amp;ldquo;Deploy&amp;rdquo;. Wait for the containers to be provisioned, then try visiting &lt;code&gt;https://&amp;lt;peertube-domain&amp;gt;/&lt;/code&gt; to see if it deployed successfully.&lt;/p&gt;
&lt;p&gt;The last thing you&amp;rsquo;ll need to do is &lt;a href=&#34;https://docs.joinpeertube.org/install/docker#obtaining-your-automatically-generated-admin-credentials&#34;&gt;get the generated password&lt;/a&gt; for the &lt;code&gt;root&lt;/code&gt; user. You can do that by clicking the &amp;ldquo;Logs&amp;rdquo; tab, getting the recent log messages from the &amp;ldquo;PeerTube&amp;rdquo; container, and doing a find-in-page search for &amp;ldquo;User password&amp;rdquo;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113553.png&#34; width=&#34;600&#34; height=&#34;353&#34; alt=&#34;Auto-generated description: A dark-themed dashboard interface displays log entries for PeerTube, showing various status messages and timestamps.&#34;&gt;
&lt;p&gt;Alternatively, you can set the password yourself by going to the Terminal, connecting to the PeerTube container, and typing in the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm run reset-password -- -u root
&lt;/code&gt;&lt;/pre&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113518.png&#34; width=&#34;600&#34; height=&#34;358&#34; alt=&#34;Auto-generated description: A terminal window is open on a dashboard interface with a command to reset a password being displayed.&#34;&gt;
&lt;p&gt;You then should be able to login into the frontend as &amp;ldquo;root&amp;rdquo; and continue setting up your instance from there.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250301-113600.png&#34; width=&#34;600&#34; height=&#34;355&#34; alt=&#34;Auto-generated description: A login page for PeerTube is displayed, featuring fields for username and password, and links for terms of service and password recovery.&#34;&gt;
&lt;p&gt;At this point, you should have a functioning PeerTube instance. The rest of the setup you can do from the web interface.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need to perform additional steps to get email working; and you&amp;rsquo;ll need to make additional changes if you want to host your videos in an object store, and potentially use some of the advanced features like uploading via Torrent or publishing a live. I&amp;rsquo;ve not done any of these myself, but information on how to do this should be findable within &lt;a href=&#34;https://docs.joinpeertube.org&#34;&gt;PeerTube&amp;rsquo;s documentation page&lt;/a&gt;. The Docker Compose file should be editable from within Coolify; the trick is finding out which environment variables you&amp;rsquo;ll need to set (and just beware that Docker compose &amp;ldquo;normalises&amp;rdquo; the YAML file so you may need to adjust environment variables in both the &lt;code&gt;x-env&lt;/code&gt; and the service environments section). But hopefully this should be enough if all you want to do is host videos on your own infrastructure.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/02/25/gave-dave-winers-wordland-a.html</link>
      <pubDate>Tue, 25 Feb 2025 21:23:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/25/gave-dave-winers-wordland-a.html</guid>
      <description>&lt;p&gt;Gave Dave Winer&amp;rsquo;s &lt;a href=&#34;https://wordland.social/&#34;&gt;Wordland&lt;/a&gt; a try today. I like it. It&amp;rsquo;s quite a nice writing environment to work in. Wrote a couple of long form posts in it, along with a few that were a paragraph or two, and the editor felt great to use. Does a good job growing with the length of the piece too.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250225-155319.png&#34; width=&#34;600&#34; height=&#34;370&#34; alt=&#34;Auto-generated description: A text editor displays a guide titled How To Start A Naming Convention, discussing strategies for naming conventions in technical environments.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Making A Small Two-Letter Country Code Lookup Page</title>
      <link>https://lmika.org/2025/02/20/made-a-small-thing-yesterday.html</link>
      <pubDate>Thu, 20 Feb 2025 08:57:14 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/20/made-a-small-thing-yesterday.html</guid>
      <description>&lt;p&gt;Made a small thing yesterday.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been seeing links to the &lt;a href=&#34;https://uchu.style&#34;&gt;uchū color pallette&lt;/a&gt; and &lt;a href=&#34;https://www.brailleinstitute.org/freefont/&#34;&gt;Atkinson Hyperlegible font&lt;/a&gt; on Mastodon this past week and I wanted to give them a try on something. With little else to work on, I thought I&amp;rsquo;d spend yesterday evening building a small thing I&amp;rsquo;ve been wishing I had at work for a while.&lt;/p&gt;
&lt;p&gt;My tasks at work has me looking up the two-letter country codes a lot recently. My goto is the &lt;a href=&#34;https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2&#34;&gt;ISO-3166-1 alpha 2&lt;/a&gt; page on Wikipedia, but getting there involves a few clicks, a web-search, and a &amp;ldquo;find on page.&amp;rdquo; I wanted an easier way to get to this list, and a much easier way to filter it. I also wanted something that could work as a Vivaldi side panel, so that I can call it up while I&amp;rsquo;m looking at something else.&lt;/p&gt;
&lt;p&gt;What I ended up building is this, which I&amp;rsquo;ve called 2LCC, which stands for Two Letter Country Codes.&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250220-083135.png&#34;
     
        alt=&#34;Auto-generated description: A web page displays a country code list, featuring a filter option and countries listed alphabetically with their corresponding codes.&#34; 
     
      /&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250220-083206.png&#34;
     
        alt=&#34;Auto-generated description: A webpage shows a table listing country codes and names, including IO for British Indian Ocean Territory, IN for India, and ID for Indonesia, with Ind typed in a search bar.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Start entering a country name or country code to filter the table.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250220-083226.png&#34;
     
        alt=&#34;Auto-generated description: A selection list on a web page shows two-letter country codes along with their corresponding country names, with AU for Australia highlighted.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Because two-letter codes will appear as letters within country names, any exact match in either the code or the country name will highlight the row in yellow.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2025/out-20250220-083239.png&#34;
     
        alt=&#34;Auto-generated description: A web browser window is open displaying a list of country codes on the left and several website shortcuts on the right, including Booking.com, News.com.au, YouTube, and eBay.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  How it looks in Vivaldi&amp;#39;s sidebar.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;It was built using Hugo, which some might think may be a bit overkill, but Hugo does has some nice utilites for &lt;a href=&#34;https://gohugo.io/methods/site/data/&#34;&gt;reading JSON data&lt;/a&gt; and running it through a Go template to produce static HTML. And that&amp;rsquo;s all this is: a static HTML page with a big table of country codes with a smattering of CSS and JavaScript. The JavaScript is just vanilla, and is simply used to power the filter, which basically walks the table, hiding rows which don&amp;rsquo;t contain the filter string. I was originally going to have the filter logic test the inner text elements of the table cells, but I decided to have the template spit out the country code as &lt;code&gt;data-&lt;/code&gt; attributes instead. It&amp;rsquo;s easier to fetch in JavaScript using the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset&#34;&gt;dataset&lt;/a&gt; property list and since the values are spat out by the template in lowercase, it makes case insensitive slightly easier.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll see how often I end up using this: there&amp;rsquo;ve been instances when I&amp;rsquo;ve over-index needing something for my job, only to build it and find myself using it less than I imagined. It probably wasn&amp;rsquo;t a great project to choose for trying out uchū colour scheme, seeing that I only ended up using two colours. But I&amp;rsquo;m happy with how the two colours I used turned out, and the the font looks great. And I do like that I took on more of the layout styling myself, rather than simply defer to &lt;a href=&#34;https://simplecss.org&#34;&gt;simple.css&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a link to it should this be useful to you: &lt;a href=&#34;https://2lcc.lmika.app/&#34;&gt;https://2lcc.lmika.app/&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/02/15/dusted-off-podcast-favourites-last.html</link>
      <pubDate>Sat, 15 Feb 2025 11:10:08 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/15/dusted-off-podcast-favourites-last.html</guid>
      <description>&lt;p&gt;Dusted off Podcast Favourites (last commit 25 April 2022) and fixed a longstanding issue of thumbnails being lost when they&amp;rsquo;re changed in the feed. Editing the feed properties will now force a refresh of the thumbnail URLs. Didn&amp;rsquo;t need to change anything else, which was a nice change.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250215-110539.png&#34; width=&#34;600&#34; height=&#34;537&#34; alt=&#34;Auto-generated description: A podcast favourites list showing episodes with titles and descriptions from the ATP - Members Feed.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/02/13/prefer-numbered-lists-to-bullets.html</link>
      <pubDate>Thu, 13 Feb 2025 07:48:59 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/13/prefer-numbered-lists-to-bullets.html</guid>
      <description>&lt;p&gt;🔗 &lt;a href=&#34;https://www.dannyguo.com/blog/prefer-numbered-lists-to-bullets&#34;&gt;Prefer Numbered Lists to Bullets&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Good arguments for using numbered listed instead of bullets in chat communication. I don&amp;rsquo;t disagree with any of them. I will say that tend to preferred bulleted lists simply because the chat apps I use tend to make using numbered lists more difficult than it should be. Slack, for example, only starts a &amp;ldquo;real&amp;rdquo; numbered list when it detects you type &lt;code&gt;1.&lt;/code&gt;. And once you&amp;rsquo;ve started, there&amp;rsquo;s no way to skip ordinals within the same numbered list.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250213-074522.png&#34; width=&#34;600&#34; height=&#34;395&#34; alt=&#34;Auto-generated description: A chat message from Leon Mika lists items with different numbers and includes a section to jot something down.&#34;&gt;
&lt;figcaption&gt;Note that &#34;1. This&#34; the only &#34;real&#34; numbered list, and has a different appearance.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Even Obsidian&amp;rsquo;s implementation is not perfect. Despite making it easy to &lt;a href=&#34;https://lmika.org/2025/02/11/oh-thats-nice-looks-like.html&#34;&gt;start a numbered list at an arbitrary ordinal&lt;/a&gt;, it&amp;rsquo;s still not possible to skip ordinals.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;d be simpler if they didn&amp;rsquo;t try to automatically make &amp;ldquo;real&amp;rdquo; numbered lists at all.&lt;/p&gt;
&lt;p&gt;Via: &lt;a href=&#34;https://mastodon.social/@jimniels/113992264842886953&#34;&gt;Jim Nielsen&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/02/04/trying-out-bayou-theme-on.html</link>
      <pubDate>Tue, 04 Feb 2025 07:55:29 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/04/trying-out-bayou-theme-on.html</guid>
      <description>&lt;p&gt;Trying out &lt;a href=&#34;https://bayou.micro.blog&#34;&gt;Bayou theme&lt;/a&gt; by @Mtt on a test blog. Lots to like about it, especially the idea of having the latest micro-post appear in the form of a status message. Very unique.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250204-075258.png&#34; width=&#34;600&#34; height=&#34;633&#34; alt=&#34;Auto-generated description: A minimal blog page titled Leon Mika features categories like About, Archive, and Replies, along with a post mentioning categories being sorted and three other listed posts from 4 February 2025.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/02/03/already-making-daily-note-archives.html</link>
      <pubDate>Mon, 03 Feb 2025 07:58:01 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/02/03/already-making-daily-note-archives.html</guid>
      <description>&lt;p&gt;Already making daily note archives for 2025.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250203-075338.png&#34; width=&#34;392&#34; height=&#34;463&#34; alt=&#34;Auto-generated description: A computer interface displaying a folder structure for daily notes from 2022 to 2025, highlighting a note dated January 6, 2025.&#34; class=&#34;block-center&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/29/okay-i-think-i-know.html</link>
      <pubDate>Wed, 29 Jan 2025 16:38:36 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/29/okay-i-think-i-know.html</guid>
      <description>&lt;p&gt;Okay, I think I know why I stopped playing Wordle.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250129-163141.png&#34; width=&#34;600&#34; height=&#34;1303&#34; alt=&#34;Auto-generated description: A screenshot of a word-guessing game shows the word TOCK in progress with several guessed words, color-coded hints, and navigation buttons.&#34; class=&#34;block-center&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/26/finding-that-styling-a-page.html</link>
      <pubDate>Sun, 26 Jan 2025 10:41:50 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/26/finding-that-styling-a-page.html</guid>
      <description>&lt;p&gt;Finding that styling a page with &lt;code&gt;min-height: 100vh&lt;/code&gt; causes the need to scroll when I open the page in Vivaldi Mobile, as &lt;code&gt;vh&lt;/code&gt; does not recognise vertical space taken up by toolbars. What I actually want is &lt;code&gt;100dvh&lt;/code&gt; (i.e. &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/CSS/length#dynamic&#34;&gt;dynamic view-height&lt;/a&gt;) which does. Found this slide helpful (&lt;a href=&#34;https://youtu.be/Xy9ZXRRgpLk?t=982&#34;&gt;source&lt;/a&gt; and &lt;a href=&#34;https://stackoverflow.com/a/72245072&#34;&gt;via&lt;/a&gt;).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/dvh-slide.png&#34; width=&#34;600&#34; height=&#34;398&#34; alt=&#34;Auto-generated description: Three smartphones display different viewport height measurements labeled as dynamic, largest, and smallest, with Google&#39;s branding in the corner.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/25/this-weeks-distraction-building-a.html</link>
      <pubDate>Sat, 25 Jan 2025 16:03:08 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/25/this-weeks-distraction-building-a.html</guid>
      <description>&lt;p&gt;This week&amp;rsquo;s distraction: building a Wordle clone. No particular reason for doing this other than I felt like building one, although I did miss the small time waster of the original Wordle, and watching a game show with my parents that had a similar concept just made those feelings stronger. Main difference between this and Wordle classic: board randomly selects between 4-letter, 5-letter, and 6-letter words; no daily limit or social-media sharing when you guessed the word correctly; and the biggest one: UK English spelling.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250125-141717.png&#34; width=&#34;460&#34; height=&#34;963&#34; alt=&#34;Auto-generated description: A word puzzle game interface shows a grid with the words HOUSE, ALTAR, and POINT, with colour-coded tiles indicating correct and incorrect letter guesses.&#34; class=&#34;block-center&#34;&gt;
&lt;p&gt;Some remarks on how this was built: I used 11ty to build the static site. It originally started as just a HTML page with some JavaScript, but I wanted to leave the option open for bundling and minifying the JS with Stimulus. The dictionary I got from &lt;a href=&#34;https://archive.netbsd.org/pub/pkgsrc-archive/distfiles/2019Q4/hunspell-dictionaries/en_GB-20061130/&#34;&gt;Hunspell&lt;/a&gt;, which &lt;a href=&#34;https://apple.stackexchange.com/a/402862&#34;&gt;is apparently the spell checker&lt;/a&gt; Apple has based their work on. There is a little bit of Go to filter and sort the dictionary of words. The words are in sorted order for the binary search algorithm to check if a word exists or not. The puzzle order is predetermined and was done by &amp;ldquo;shuffling&amp;rdquo; the indices in a separate array. Base styles are, of course, from &lt;a href=&#34;https://simplecss.org/&#34;&gt;simple.css&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in checking it out, &lt;a href=&#34;https://letterise.lmika.app&#34;&gt;you can find it here&lt;/a&gt;. Just be aware that it may not be as polished as much of the other stuff you find out there. Turns out that I can tolerate a fair few shortcomings in things that I build for my own amusement.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/08/keyboard-maestro-is-coming-into.html</link>
      <pubDate>Wed, 08 Jan 2025 11:07:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/08/keyboard-maestro-is-coming-into.html</guid>
      <description>&lt;p&gt;Keyboard Maestro is coming into its own as a way for scheduling recurring tasks. I&amp;rsquo;ve just set one up for a daily report I need to run. It&amp;rsquo;s little more than a shell script so I probably could&amp;rsquo;ve used crontab to do it, but is so much easier configuring and testing it in Keyboard Maestro.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250108-110533.png&#34; width=&#34;600&#34; height=&#34;575&#34; alt=&#34;Auto-generated description: A scheduled report configuration window is shown, with options for date and time selection, and a shell script execution setup displayed.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/05/i-recently-got-a-new.html</link>
      <pubDate>Sun, 05 Jan 2025 17:10:47 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/05/i-recently-got-a-new.html</guid>
      <description>&lt;p&gt;I recently got a new phone, a Pixel 9 Pro, which meant I needed to bring Alto Player up to date. I probably could&amp;rsquo;ve gotten away using the version I was using on my Pixel 6. But I didn&amp;rsquo;t have a binary build, and I needed to upgrade Gradle anyway, so I decided to spend a bit of time bringing it up to date to API version 35, the version used in Android 15.0. Fortunately it was only a few hours in total, and once I got it running in the simulator, I side-loaded it onto my phone and started using it.&lt;/p&gt;
&lt;p&gt;It worked, but there were some significant UI issues. The title-bar bled into the status bar, the album image in the Now Playing widget was cropped by the curved corners of the phone, and the media notification didn&amp;rsquo;t display playback controls.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250105-162220.png&#34; width=&#34;600&#34; height=&#34;404&#34; alt=&#34;Auto-generated description: A mobile app displays a list of music tracks, each with a title, duration, and album art symbol.&#34;&gt;
&lt;figcaption&gt;Evolution of the window insets, from left-to-right: before any changes, version with the album cover and margin, final version with no album art.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I set about fixing these issues today, starting with the title-bar and Now Playing widget. These was an issue with the views not respecting the window insets, and after a quick Google search, I found &lt;a href=&#34;https://developer.android.com/develop/ui/views/layout/insets&#34;&gt;this article&lt;/a&gt; showing how one could resolve this by adding a &lt;a href=&#34;https://developer.android.com/reference/androidx/core/view/ViewCompat#setOnApplyWindowInsetsListener%28android.view.View%2Candroidx.core.view.OnApplyWindowInsetsListener%29&#34;&gt;ViewCompat.setOnApplyWindowInsetsListener&lt;/a&gt; and reacting to it by adjusting the margins of the view.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;val&lt;/span&gt; topLevelLayout = findViewById(&lt;span style=&#34;color:#a6e22e&#34;&gt;R&lt;/span&gt;.id.top_level_layout) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; CoordinatorLayout
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ViewCompat&lt;/span&gt;.setOnApplyWindowInsetsListener(topLevelLayout) { v, windowInsets &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;val&lt;/span&gt; insets = windowInsets.getInsets(&lt;span style=&#34;color:#a6e22e&#34;&gt;WindowInsetsCompat&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Type&lt;/span&gt;.systemBars())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    v.updateLayoutParams&amp;lt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ViewGroup&lt;/span&gt;.MarginLayoutParams&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        leftMargin = insets.left
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bottomMargin = insets.bottom
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        rightMargin = insets.right
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// applying a top margin here would work, but will not have the toolbar
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// background colour &amp;#34;bleed&amp;#34; into the status bar, which is what I want.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;val&lt;/span&gt; t = v.findViewById&amp;lt;Toolbar&amp;gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;R&lt;/span&gt;.id.toolbar)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t.updateLayoutParams&amp;lt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ViewGroup&lt;/span&gt;.MarginLayoutParams&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// make the toolbar a little &amp;#34;narrower&amp;#34; than full height
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        topMargin = insets.top * &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; / &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;WindowInsetsCompat&lt;/span&gt;.CONSUMED
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It took a few attempts, but I managed to get this working. Just using the top inset for the toolbar margin made it a little larger than I liked, so I adjusted the height to be 75% of the inset. This means the toolbar will actually encroach into the area reserved for cut-outs like the front-facing camera. This is arguably not something a &amp;ldquo;real&amp;rdquo; Android apps should do, but this is just for me and my phone so it&amp;rsquo;s fine.&lt;/p&gt;
&lt;p&gt;I went through a few iterations of the album artwork cutoff on the bottom right corner trying to find something I liked. I tried bringing in the horizontal margins a little, but I didn&amp;rsquo;t like the alignment of the album art in the player, particularly compared to the covers that appear in the track list screen. One thing I didn&amp;rsquo;t try was raising the bottom margin so that it would fit &amp;ldquo;above&amp;rdquo; the curve. But those corners curve in quite a bit, and doing this would sacrifice a lot of vertical space. So I settled on hiding the album art altogether. It&amp;rsquo;s a bit of a shame to loose it, but at least it looks neater now.&lt;/p&gt;
&lt;p&gt;The next thing I looked at was fixing the playback controls in the media notification. After some investigating, I found that this was because I was not setting the available actions in the &lt;a href=&#34;https://developer.android.com/reference/android/support/v4/media/session/PlaybackStateCompat.Builder#setActions%28long%29&#34;&gt;PlaybackStateCompat builder&lt;/a&gt;. This, if I understand correctly, is used to communicate to various systems the current state of the playing media: what the track name is, whether it&amp;rsquo;s playing, whether one can skip forward or back. I have my own types for tracking this information — which is probably not correct but I wasn&amp;rsquo;t completely sure as to what I was doing at the time with Android&amp;rsquo;s media stack&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; — and when I needed to convert this to a type understood by the framework, I made instances of this builder without setting the available actions. Earlier versions of Android seemed not to care, and the controls always appeared on the notification. But I guess they changed that.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250105-162302.png&#34; width=&#34;600&#34; height=&#34;404&#34; alt=&#34;Three smartphone screens display a notification panel, a permission request, and a media notification with music controls.&#34;&gt;
&lt;figcaption&gt;Evolution of the playback notification, from left-to-right: before any changes, the request to show notifications upon first launch (this is defined by the system), the playback notifications with controls again.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;One other thing I needed to do was to explicitly ask the user permission to show a notification before I could publish one. This is also relatively new: my experience with Android goes back to the early days where these permissions were disclosed up front when the app was installed. But I can completely understand why they changed it, as it was easy to simply tap through those screens with reading them. I am wondering whether media playback notifications are in some way exempt from these permission checks, as I was actually getting one to appear before I made this changes. But I figured it was probably worth doing anyway, so I added this permission request on first launch. Arguably I should be asking for this permission when playback starts, but again, this is just for me.&lt;/p&gt;
&lt;p&gt;One final thing I needed to address were long album titles. The text view displaying the the album title had a width that autosized to the title itself, and the start and end constraints were set such that it appears centred in the view. This worked for &amp;ldquo;normal&amp;rdquo; length titles but when the length became excessive, the text view would fill the entire width of the screen and the title will appear left justified.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250105-162320.png&#34; width=&#34;600&#34; height=&#34;606&#34; alt=&#34;Auto-generated description: Two smartphone screens display a music player with Leaps And Bounds by Paul Kelly.&#34;&gt;
&lt;figcaption&gt;Before (left) and after (right) shot of the fixed album title.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The fix for this was to set the text width to be calculated by the start and end constraints (setting &lt;code&gt;layout_width&lt;/code&gt; to &lt;code&gt;0dp&lt;/code&gt;), bringing in the margins a little, and making the label text centre justified. I did this already for the track title, so it was easy to do this here too. Not sure why I didn&amp;rsquo;t do it earlier, or why I haven&amp;rsquo;t done it for the artist&amp;rsquo;s name yet.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250105-162740.png&#34; width=&#34;600&#34; height=&#34;391&#34; alt=&#34;Auto-generated description: A screenshot of a mobile app layout design in Android Studio, featuring a user interface editor with text, images, and buttons.&#34;&gt;
&lt;p&gt;This was all rushed, and I&amp;rsquo;ll admit I wasn&amp;rsquo;t 100% sure what I was doing. I was going down the route of trial-and-error to get this working, mixed in with web searches and a trip to ChatGPT. And yeah, the methods I used won&amp;rsquo;t make this a portable Android app that would work on every phone out there. But I&amp;rsquo;m reasonably happy with how it turned out.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;This is still true to this day.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2025/01/04/thanks-for-my-new-found.html</link>
      <pubDate>Sat, 04 Jan 2025 15:25:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2025/01/04/thanks-for-my-new-found.html</guid>
      <description>&lt;p&gt;Thanks for my new found fondness of buying mainstream music instead of streaming it, I needed a way to get these albums into Alto Catalogue. There exists a feature for fetching and importing tracks from a Zip referenced by a URL. This works great for albums bought in Bandcamp, but less so for any tracks I may have on my local machine.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2025/out-20250104-151341.png&#34; width=&#34;600&#34; height=&#34;427&#34; alt=&#34;A web interface for uploading a zip file from a URL with fields for artist, album, and default rating.&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve managed to get Alto Catalogue building again after updating Webpack and a few NPM packages, so in theory, I could  add an Upload Zip file action. But there&amp;rsquo;s more to this than simply accepting and unpacking a Zip file. I have to read the metadata, maybe even preview the tracks that will be imported, just in case I&amp;rsquo;m importing something I rather not (I did see this once, where zipping a bunch of tracks in the Finder introduced duplicates). This already exists for Zip files that are downloadable online.&lt;/p&gt;
&lt;p&gt;I had a though about what my options are, until I remembered that I had a &lt;a href=&#34;https://github.com/Forceu/Gokapi/&#34;&gt;Gokapi instance&lt;/a&gt; running in Pikapods. So I tried using that to temporarily host the Zip file with a publicly available URL that could be read by Alto Catalouge.&lt;/p&gt;
&lt;p&gt;The only problem is my internet upload speed is &lt;em&gt;sooooo sloooooow&lt;/em&gt;. The Gokapi instance is hosted in Europe, and I suspect the instance itself is a little underpowered. So uploading 100 MB Zip files would take a fair bit of time: maybe 15-30 minutes. When I tried doing this via the web frontend, the connection timed out.&lt;/p&gt;
&lt;p&gt;Fortunately, &lt;a href=&#34;https://gokapi.readthedocs.io/en/latest/advanced.html#api&#34;&gt;Gokapi has an API&lt;/a&gt; and one of the methods allows you to upload a file in &amp;ldquo;chunks,&amp;rdquo; which Gokapi will assemble back into the original file. Even better is that this chunking can be uploaded in parallel.&lt;/p&gt;
&lt;p&gt;So I built a CLI tool which made of this chunking API to upload the Zip files. Once the upload is complete, the tool will display the hot-link URL, which I can copy-and-paste into Alto Catalogue.&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://media.lmika.org/videos/2025/send2gokapi-screencast.mp4&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The whole process isn&amp;rsquo;t fast (again, slow upload speeds). But it works, and I can use this tool to queue a bunch of uploads and let it do its thing while I&amp;rsquo;m doing something else. I really like tools that do this, where you&amp;rsquo;re not forced to babysitting them through the process.&lt;/p&gt;
&lt;p&gt;There are a few limitations with it. It doesn&amp;rsquo;t allow for an awful lot of customisations on the lifecycle of the uploaded file. And the tool stalled out once when my computer went to sleep, and I had to start the upload from scratch. I could probably add something to track the chunks that were successful, allowing one to continue a stalled upload. If this happens frequently, I may look more into adding this.&lt;/p&gt;
&lt;p&gt;But even so, this could be a useful addition to my use of Gokapi for transferring temporary files. If you think this might be useful to you, &lt;a href=&#34;https://lmika.dev/cmd/send2gokapi&#34;&gt;you can find the tool here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Home Screen Of 2024</title>
      <link>https://lmika.org/2024/12/29/home-screen-of.html</link>
      <pubDate>Sun, 29 Dec 2024 21:51:21 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/29/home-screen-of.html</guid>
      <description>&lt;p&gt;It’s just turned 3:00 in the afternoon, and I was alternating between the couch and the computer desk, racking my brain on what to do. With no ongoing projects — a few ideas have been  bouncing around, yet none has grabbed me so far, and I had nothing else in a state where I could just slip on some music or a podcast and work on — and seeing a few others make similar posts on their blogs, I’d figured I talk about my home screens.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241229-214649.png&#34; width=&#34;600&#34; height=&#34;414&#34; alt=&#34;A smartphone home screen features various app icons arranged across three panels, set against a blue floral background.&#34;&gt;
&lt;p&gt;I realised that I haven’t actually done this before, mainly because my home screens change very slowly (the background hardly ever). Dramatic changes usually come about when I’m setting up a new phone.&lt;/p&gt;
&lt;p&gt;And yet, I do want to talk a little about the apps I have at the moment, and I did want to make sure I had a record of how the home screens looked. And seeing that I needed to keep myself occupied doing something, now is as good a time as any.&lt;/p&gt;
&lt;h2 id=&#34;screen-one&#34;&gt;Screen One&lt;/h2&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/phone-screenshot-1.png&#34; width=&#34;600&#34; height=&#34;1300&#34; alt=&#34;Auto-generated description: A mobile home screen displays weather information, calendar events, and various app icons against a blurred background of yellow and gray flowers.&#34; class=&#34;block-center&#34;&gt;
&lt;p&gt;This screen contains two widgets — the date and weather widget at the top, and the calendar widget on the right — plus a small collection of apps placed deliberately where they are. The apps I have here are not necessarily the most used (although two of them are) but I like having easy access to them for various reasons.&lt;/p&gt;
&lt;p&gt;Aside from the widgets, the apps I have on this screen — from left to right, top to bottom — are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Micropub Checkin:&lt;/strong&gt; A silly little Flutter app I used for adding check-ins to &lt;a href=&#34;https://lmika.day/&#34;&gt;lmika.day&lt;/a&gt;. The apps in a bit of a neglected state, but I still use it as I get value from tracking places I’ve been.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strata:&lt;/strong&gt; The note’s app from Micro.blog. This is where I write my short-term notes. I use Google Keep for shopping lists, but everything else goes here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alto:&lt;/strong&gt; A music app I wrote, and the main music app I listen to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pocket Casts:&lt;/strong&gt; The podcast player app I use. Apart from the web-browser, this and Alto are two of the most used apps I have on my phone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VSReader:&lt;/strong&gt; Another silly little Flutter app. This is a test build for an RSS reader I was working on a couple of months ago. It’s been a while since I’ve opened this, and I probably should just kill it given that I haven’t made any recent changes to it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Wallet:&lt;/strong&gt; Google’s digital wallet (well, at least their current iteration of their digital wallet). I use it mainly for my train ticket but I do have my credit card in there, just in case I walk out without my “real” wallet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The items in the dock are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phone:&lt;/strong&gt; My family and I still use the phone quite frequently so this app has remained in the dock since I set the phone up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Messages:&lt;/strong&gt; This is Android’s messaging app. Much like the phone, I communicate with family mostly via SMS, and now RCS, messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Play Store:&lt;/strong&gt; I rarely go to the Play Store, so there’s no real need for this icon to be here. But I haven’t got around to removing it yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vivaldi:&lt;/strong&gt; My web browser of choice.&lt;/li&gt;
&lt;li&gt;The right most icon changes based on the last used app, which I’m not a huge fan of, as it occasionally changes just as I go to tap it and I launch the wrong app by mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;screen-two&#34;&gt;Screen Two&lt;/h2&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/phone-screenshot-2.png&#34; width=&#34;600&#34; height=&#34;1300&#34; alt=&#34;Auto-generated description: A smartphone screen displays various app icons arranged in a grid over a floral background.&#34; class=&#34;block-center&#34;&gt;
&lt;p&gt;A grab-bag of apps I frequently use. Some of them probably should be on the first screen, but since real-estate is at a bit of a premium I just keep them here, and swipe over when I need them.&lt;/p&gt;
&lt;p&gt;From left to right, top to bottom, the apps on this screen is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PTV:&lt;/strong&gt; The Victorian public transport app. I usually use it to know the arrival time of the tram I take going home. Also useful for trip planning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plex:&lt;/strong&gt; I generally don’t watch things on my phone, but before I got my Nvidia Shield, I used this Plex app to Chromecast shows to the TV. It was never great at it though, as it sometimes disconnected from the Chromecast session while the video was running, leaving me with no means of stopping it  until I unplugged the Chromecast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kindle:&lt;/strong&gt; Kept here as I occasionally use it to read books if I’ve read through my RSS feeds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT:&lt;/strong&gt; I don’t use ChatGPT on my phone that often, but it does occasionally come in useful when a web-search proves fruitless.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FastMail:&lt;/strong&gt; My email provider of choice. Given how often I use it, this is arguably one of those apps that should be on the first screen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pager Duty:&lt;/strong&gt; The twenty-four hours on-call paging software I had to use for work. I’m no longer on the on-call roster so it’s probably something I can remove.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WhatsApp:&lt;/strong&gt; What I use for messaging friends. I don’t like the fact that I have a Meta app on my phone, but that’s what my friends chose to use so I’m stuck with it (it’s also better than Viber, which is what we used before).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WireGuard:&lt;/strong&gt; Personal VPN, although I’m currently not using WireGuard for anything right now. I like to keep it mainly because I like the logo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discord:&lt;/strong&gt; I’m a member of a few Discord servers, but I use the mobile client mainly to check into the Hemispheric Views Discord.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notion:&lt;/strong&gt; Where I store my “long term” notes, at least for now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tusky:&lt;/strong&gt; Mastodon client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Splitwise:&lt;/strong&gt; Group expense management and splitting app. This was useful during our European trip last year, where each of us would take in turn to pay for the group.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SunSmart:&lt;/strong&gt; Used to track the current and forecasted UV index. Useful around this time of year if I’m planning to be outside for an extended period of time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Micro.blog:&lt;/strong&gt; The Micro.blog app, although I occasionally     use the web version too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1Password:&lt;/strong&gt; My password manager of choice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Realestate.com:&lt;/strong&gt; Used to browse real-estate, out of curiosity more than anything else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spotify:&lt;/strong&gt; My “secondary” music app. I don’t use it for anything that I regularly listen to, but it’s occasionally useful for those once-off tracks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Authenticator:&lt;/strong&gt; Where I keep my 2FA codes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day One:&lt;/strong&gt; Before I moved to a web-based journalling app, I used this Day One client for writing journal entries. It wasn’t perfect: there was always syncing delays to/from the Apple platform instances of Day One. But it was fine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slack:&lt;/strong&gt; Used mainly for work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Camera:&lt;/strong&gt; I’m not sure why I have this here, since I almost always use the double power-button tap to bring up the camera. I guess I moved it here from screen one and never removed it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;screen-three&#34;&gt;Screen Three&lt;/h2&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/phone-screenshot-3.png&#34; width=&#34;600&#34; height=&#34;1300&#34; alt=&#34;Auto-generated description: A smartphone home screen displays a vibrant wallpaper of yellow flowers and foliage, with apps like Booking, Airalo, and Emirates icons visible at the top.&#34; class=&#34;block-center&#34;&gt;
&lt;p&gt;This is a screen I hardly ever used, as it’s mainly reserved for apps that are useful while travelling. The &lt;strong&gt;Booking.com&lt;/strong&gt; app and &lt;strong&gt;Emirates&lt;/strong&gt; apps I can probably remove: I was using them mainly to track flights and accomodation during my European trip last year.&lt;/p&gt;
&lt;p&gt;The only one worth keeping is &lt;strong&gt;Airalo&lt;/strong&gt;, which allows you to buy and setup data SIMs that work overseas. This has been really useful to me during my last couple of trips, and I hope to keep using it for trips in the future. It doesn’t offer a lot of data, but any data is better than zero data, as my friends — who continued asking to use &lt;em&gt;my&lt;/em&gt; data when we’re out of WiFi range — can attest.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/26/released-version-of-sidebar-for.html</link>
      <pubDate>Thu, 26 Dec 2024 21:09:14 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/26/released-version-of-sidebar-for.html</guid>
      <description>&lt;p&gt;Released version 1.2.0 of Sidebar for Tiny Theme. In this version, the sidebar can now be configured to appear on pages other than just the home page. Options include showing it on the pages of posts, or pages other than posts. With both on, the sidebar will now appear on all pages of the site.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/cleanshot-2024-12-26-at-15.40.11.png&#34; width=&#34;378&#34; height=&#34;179&#34; alt=&#34;Two checkbox options are available for showing sidebar on posts and other pages, along with &#39;Back&#39; and &#39;Update Settings&#39; buttons.&#34; class=&#34;block-center max-half-width&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/23/thought-id-have-another-go.html</link>
      <pubDate>Mon, 23 Dec 2024 10:10:19 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/23/thought-id-have-another-go.html</guid>
      <description>&lt;p&gt;Thought I&amp;rsquo;d have another go at looking at BoxedWine for making an online archive of my old Delphi projects. They&amp;rsquo;ve been some significant improvements since &lt;a href=&#34;https://lmika.org/2023/09/29/after-having-success.html&#34;&gt;the last time I looked at it&lt;/a&gt;. They don&amp;rsquo;t run fast, but that&amp;rsquo;s fine. As long as they run.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241223-100322.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: A digital card game is being played on a computer screen, featuring several cards displayed in a grid layout.&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241223-100540.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: A game screen of Tetris is displayed, showing falling blocks and score details on the right side.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>That Which Didn&#39;t Make The Cut</title>
      <link>https://lmika.org/2024/12/20/the-cutting-room.html</link>
      <pubDate>Sat, 21 Dec 2024 09:38:09 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/20/the-cutting-room.html</guid>
      <description>&lt;p&gt;I did a bit of a clean-up of my projects folder yesterday, clearing out all the ideas that never made it off the ground. I&amp;rsquo;d figured it&amp;rsquo;d be good to write a few words about each one before erasing them from my hard drive for good.&lt;/p&gt;
&lt;p&gt;I suppose the healthiest thing to do would be to just let them go. But what can I say? Should a time come in the future where I wish to revisit them, it&amp;rsquo;d be better to have something written down than not. It wouldn&amp;rsquo;t be the first time I wished this was so.&lt;/p&gt;
&lt;p&gt;Anyway, here are the ones that were removed today. I don&amp;rsquo;t have dates of when these were made or abandoned, but it&amp;rsquo;s likely somewhere between 2022 and 2024.&lt;/p&gt;
&lt;h2 id=&#34;interlaced&#34;&gt;Interlaced&lt;/h2&gt;
&lt;p&gt;This was an idea for a YouTube client&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that would&amp;rsquo;ve used YouTube&amp;rsquo;s RSS feeds to track subscriptions. The idea came about during a time when I got frustrated with YouTube&amp;rsquo;s ads. I think it was an election year and I was seeing some distasteful political ads that really turned me off. This would&amp;rsquo;ve been a mobile app, most likely built using Flutter, and possibly with a server component to get this working with Chromecast, although I had no idea how that would work.&lt;/p&gt;
&lt;p&gt;This never got beyond the UI mock-up stage, mainly because the prospect of working on something this large seemed daunting. Probably just as well, as YouTube solved the ads problem for me, with the release of YouTube Premium.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/interlaced-ui-mockups.png&#34; width=&#34;600&#34; height=&#34;528&#34; alt=&#34;Auto-generated description: A smartphone interface mockup displays a channels list with annotations highlighting features like a navigation tab, subscription indicators, filter options, and a Chromecast button.&#34;&gt;
&lt;h2 id=&#34;red-crest&#34;&gt;Red Crest&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.manton.org/2024/12/19/everyone-thinks-they.html&#34;&gt;I thought I could build my own blogging engine&lt;/a&gt; and this is probably the closest I got (well, in recent years). This project began as an alternative frontend for Dave Winer&amp;rsquo;s Drummer, rendering posts that would be saved in OPML. But it eventually grew into something of it&amp;rsquo;s own with the introduction of authoring features.&lt;/p&gt;
&lt;p&gt;I got pretty far on that front, allowing draft posts and possibly even scheduled posts (or at least the mechanics for scheduled posts). One feature I did like was the ability to make private posts. These would be interleaved with the public ones once I logged in, giving me something of a hybrid between a blogging CMS and a private journal. It was also possible to get these posts via a private RSS feed. I haven&amp;rsquo;t really seen a CMS do something quite like this. I know of some that allow posts to be visible to certain cohorts of readers, but nothing for just the blog author.&lt;/p&gt;
&lt;p&gt;In the end, it all got a bit much. I started preparing the screen for uploading and managing media, I decided it wasn&amp;rsquo;t worth the effort. After all, there were so many other blogging CMS&amp;rsquo;s already out there that did 90% of what I wanted.&lt;/p&gt;
&lt;div class=&#34;img-gallery&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155617.png&#34;
     
        alt=&#34;Auto-generated description: A blog page titled Random Posts Of Consciousness contains several brief reflections and thoughts on privacy and writing styles.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The public post list, styled using simple.css, naturally.  I&amp;#39;m not sure how I came up with the name &amp;#39;Random Posts of Consciousness.&amp;#39; Might&amp;#39;ve just been a working title.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155630.png&#34;
     
        alt=&#34;Auto-generated description: A blog post discusses the positive impact of Google Reader shutting down on RSS services.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Click the hash to go to a single post. Simple enough.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155242.png&#34;
     
        alt=&#34;Auto-generated description: A blog page features a post about JavaScript&amp;#39;s null and undefined types, along with options for Home and Admin.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  But once you logged in, you get to see all the private posts, plus an option to add a new post straight from the home page.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155254.png&#34;
     
        alt=&#34;Auto-generated description: A blog titled Random Posts Of Consciousness discusses the differences between `null` and `undefined` types in JavaScript.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Options to edit and delete posts also appear on a single post page.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155417.png&#34;
     
        alt=&#34;Auto-generated description: A user interface displays a text editing section with a draft about JavaScript handling `null` and `undefined` values, along with buttons for Update and Details.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The post editor. This was a simple text that accepted Markdown. Titles are optional, as they should be.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155433.png&#34;
     
        alt=&#34;Auto-generated description: A text editor interface on a webpage is visible, showcasing options for publishing or setting content to private.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  Click &amp;#39;Details&amp;#39; to edit the publish date (either a past date or the future date to schedule it) and choose whether the post is private or not.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155445.png&#34;
     
        alt=&#34;Auto-generated description: A webpage interface displays a draft post section with options to edit or delete, alongside buttons for generating a private RSS link and a logout option.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The General admin section, showing Draft posts and proving access to the private RSS feed. I think the drafts table was a mock-up; I can&amp;#39;t remember draft posts being a thing at the time.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155537.png&#34;
     
        alt=&#34;Auto-generated description: A webpage showcasing an admin panel with draft and scheduled posts, including options to edit, delete, publish, or make a draft.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The Post admin section, showing any draft and scheduled posts. The scheduled post table was definitely a mock-up.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://lmika.org/uploads/2024/out-20241220-155552.png&#34;
     
        alt=&#34;Auto-generated description: A web page from a Redcrest Admin panel displays a text message stating Bunch of pretty pictures.&#34; 
     
      /&gt;

  &lt;figcaption&gt;
  
  The Media admin section, and the ultimate realisation that I didn&amp;#39;t want to work on this anymore.
  &lt;/figcaption&gt;

&lt;/figure&gt;
&lt;/div&gt;
&lt;h2 id=&#34;reno&#34;&gt;Reno&lt;/h2&gt;
&lt;p&gt;As in &amp;ldquo;Renovation&amp;rdquo;. Not much to say about this one, other than it being an attempt to make a Pipe Dreams clone. I think I was exploring a Go-based game library and I wanted to build something relatively simple. This didn&amp;rsquo;t really go any further that what you see here.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241220-155129.png&#34; width=&#34;600&#34; height=&#34;484&#34; alt=&#34;Auto-generated description: A grid of dark squares is displayed on a computer screen, with one square featuring two horizontal white lines.&#34;&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/reno-tileset.png&#34; width=&#34;128&#34; height=&#34;128&#34; class=&#34;block-center&#34; alt=&#34;Auto-generated description: A grid of interconnected circuit-like lines on a dark background.&#34;&gt;
&lt;figcaption&gt;Tileset free for anyone who wants it.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&#34;slog&#34;&gt;SLog&lt;/h2&gt;
&lt;p&gt;Short for &amp;ldquo;Structured Log&amp;rdquo;.  This was a tool for reading JSON log messages, like the ones produce by &lt;a href=&#34;https://pkg.go.dev/github.com/rs/zerolog&#34;&gt;zerolog&lt;/a&gt;. It&amp;rsquo;s always difficult to read these in a regular text editor, and to be able to list them in a table made sense to me. This one was built for the terminal but I did make a few other attempts building something for this; one using a &lt;a href=&#34;https://wails.io&#34;&gt;web-based GUI tool&lt;/a&gt;, and another as a native MacOS app. None of these went very far — turns out there&amp;rsquo;s a lot of tedious code involved — but this version was probably the furthest along before I stopped work.&lt;/p&gt;
&lt;p&gt;Despite appearing on this list, I think I&amp;rsquo;ll keep this one around. The coding might be tedious, but I still have need something like this, and spending the time to build this properly might be worth it one day.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241220-155211.png&#34; width=&#34;600&#34; height=&#34;445&#34; alt=&#34;Auto-generated description: A terminal window displays log messages with levels and a table summarizing error, ID, level, message, and time values.&#34;&gt;
&lt;h2 id=&#34;miscellany&#34;&gt;Miscellany&lt;/h2&gt;
&lt;p&gt;Here are all the others that didn&amp;rsquo;t even get to the point that warranted a screenshot or a paragraph of text:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;s3-browse:&lt;/strong&gt; a TUI tool for browsing S3 buckets. This didn&amp;rsquo;t go beyond simply listing the files of a directory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;scorepeer:&lt;/strong&gt; An attempt to make a collection of online score-cards much like the &lt;a href=&#34;https://finska.lmika.app/&#34;&gt;Finska one&lt;/a&gt; I built.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;withenv:&lt;/strong&gt; Preconfigure the environment for a command with the values of an &lt;code&gt;.env&lt;/code&gt; file (there must be something out there that does this already).&lt;/li&gt;
&lt;li&gt;About 3 aborted attempts to make a wiki-style site using Hugo (one called &amp;ldquo;Techknow Space&amp;rdquo; which I though was pretty cleaver).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m sure there&amp;rsquo;ll be more projects down the line that would receive the same treatment as these, so expect similar posts in the future.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Or possibly a Peertube client.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/19/im-not-a.html</link>
      <pubDate>Thu, 19 Dec 2024 20:31:34 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/19/im-not-a.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;m not a fan of the changes Google made to their Weather app. It assumes you&amp;rsquo;re interested in saving every location you search for as a favourite, which is not how I use search. And horizontal scrolling for the 10 day forecast? With no date?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241219-202618.png&#34; alt=&#34;A 10-day weather forecast shows a mix of sunny and rainy days, with temperatures ranging from 13°C to 34°C.&#34;&gt;
&lt;p&gt;No, sorry. This is a step backwards in design.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/17/exploring-godot-to.html</link>
      <pubDate>Tue, 17 Dec 2024 22:00:25 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/17/exploring-godot-to.html</guid>
      <description>&lt;p&gt;Exploring Godot to see if I could use it to make a card game. I got this far:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241217-215736.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Auto-generated description: A computer window titled SolitaireGolf (DEBUG) is displayed with a blank dark screen.&#34;&gt;
&lt;p&gt;Yep, I&amp;rsquo;m on a roll. 😄&lt;/p&gt;
&lt;p&gt;Might need to work through a couple Godot tutorials first, just so that I understand the basics.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/12/i-plan-to.html</link>
      <pubDate>Thu, 12 Dec 2024 08:48:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/12/i-plan-to.html</guid>
      <description>&lt;p&gt;I plan to integrate &lt;a href=&#34;https://workpad.dev/categories/tcl&#34;&gt;UCL&lt;/a&gt; into another tool at work, so I spent last night improving it&amp;rsquo;s use as a REPL. Added support for onboard help and setting up custom type printing, which is useful for displaying tables of data. I started working on the tool today and it&amp;rsquo;s already feeling great.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241212-084242.png&#34; width=&#34;600&#34; height=&#34;465&#34; alt=&#34;A command line interface is displayed, showing help-related commands, usage, arguments, and details.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/11/if-theres-one.html</link>
      <pubDate>Wed, 11 Dec 2024 08:10:41 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/11/if-theres-one.html</guid>
      <description>&lt;p&gt;If there&amp;rsquo;s one thing I&amp;rsquo;d like to see added to Go for 2025, it&amp;rsquo;s type parameter support on methods. It&amp;rsquo;s still not possible to do something like this:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241211-080345.png&#34; width=&#34;600&#34; height=&#34;182&#34; alt=&#34;Auto-generated description: A code snippet shows a Go language function with an error message indicating that a method cannot have type parameters.&#34;&gt;
&lt;p&gt;Which is a real shame, as I&amp;rsquo;ve got some ideas on how I could use these. Building something like &lt;a href=&#34;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html&#34;&gt;Java streams&lt;/a&gt;, for example.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/05/some-more-work.html</link>
      <pubDate>Sat, 07 Dec 2024 11:05:54 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/05/some-more-work.html</guid>
      <description>&lt;p&gt;Some more work on that feed reader app. You know how I said that I wanted to hold off looking at appearances? Well, I abandoned that approach when I installed the app on my phone and tried viewing a few articles. And oof! There&amp;rsquo;s work to be done there.&lt;/p&gt;
&lt;p&gt;First, it&amp;rsquo;s slow. Very slow. It&amp;rsquo;s taking a good second or two to pull feeds and entries from Feedbin. I was expecting this, and I&amp;rsquo;ve got a few plans on how to speed this up. But the biggest concern is the janky list scrolling. I mean, I wasn&amp;rsquo;t expecting the buttery smoothness of iPhone list scrolling, but I expected Flutter to be better than what I was experiencing. I&amp;rsquo;m hoping that it&amp;rsquo;s just because I was running a debug build, but part of me fears that Flutter is just not optimised for smooth list scrolling, favouring ease of development and a common runtime. I rather not change frameworks now, especially after spending an evening dealing with all the build issues, but I don&amp;rsquo;t want to live with this for ever.&lt;/p&gt;
&lt;p&gt;But speed is not the biggest issue. The biggest offender was the feed reader view. The embedded web-view was only lightly styled, and it felt like it. The margins were all off, and I didn&amp;rsquo;t like the default font or colours. It made reading the article a bad experience to a surprising degree. I&amp;rsquo;ve dealt with rushed or poorly designed UIs in the past, but I didn&amp;rsquo;t have much tolerance for this. Not sure why this is, but I suspect it&amp;rsquo;s because I&amp;rsquo;ve been using feed readers that put some effort into the design of their reader screen.&lt;/p&gt;
&lt;p&gt;In any case, a couple of evenings ago, I decided to put some effort into the styling. I replace the body text font with Noto Sans and the fixed-font with Ubuntu Mono. I dropped the font-size a little to 1.05 em (it was previously 1.1 em, with felt a little big, and 1.0 em felt a little small). I bought the margins in a little. And I styled the block-quote, figure, and pre elements to an appearance that, despite being a little overused, felt quite modern.&lt;/p&gt;
&lt;p&gt;The results look much better, at least to my eye (and my emulator). Here are some side-to-side comparison shots of the changes (left side is the unstyled version, while the right side has the new styling changes):&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241207-105808.png&#34; width=&#34;600&#34; height=&#34;401&#34; alt=&#34;Auto-generated description: Two smartphone screens display an article titled Apple Intelligence by Bitsplitting, discussing Apple&#39;s upcoming product announcement.&#34;&gt;
&lt;figcaption&gt;Demonstration of the new font and link colouring choices.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241207-105928.png&#34; width=&#34;600&#34; height=&#34;401&#34; alt=&#34;Auto-generated description: Side-by-side comparison of two smartphone screens displaying an email titled From the Department of Bringing Receipts to the Interview, featuring text from a Stanford Review interview with President Levin.&#34;&gt;
&lt;figcaption&gt;Demonstration of the changes to block-quotes. Having a line down the left-sided is a pretty common style, but it&#39;s one I like.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241207-110005.png&#34; width=&#34;600&#34; height=&#34;401&#34; alt=&#34;Auto-generated description: Two mobile screens showing a website with a summer dark theme, including CSS code for customizing the theme&#39;s appearance.&#34;&gt;
&lt;figcaption&gt;Demonstration of code blocks and figures with captions.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I still need to actually install this on my phone and try it out. I&amp;rsquo;m wondering whether I should do so after a bit more work syncing the read status with Feedbin. That&amp;rsquo;s a feature that&amp;rsquo;s keeping me on Feedbin&amp;rsquo;s PWA for now.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/12/03/ran-into-a.html</link>
      <pubDate>Tue, 03 Dec 2024 22:45:18 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/12/03/ran-into-a.html</guid>
      <description>&lt;p&gt;Spent the last few evenings continuing work on a Flutter-based RSS feed reader. This project is now further along then my previous attempts at doing this. I&amp;rsquo;m at the point where feeds and feeds items are being fetch from Feedbin and displayed in a list view:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-20241203-212954.png&#34; width=&#34;600&#34; height=&#34;1266&#34; alt=&#34;Auto-generated description: A smartphone screen displays a list of feeds with titles like And now it’s all this, App Defaults, and Articles on Jose M.&#34; class=&#34;block-center&#34;&gt;
&lt;figcaption&gt;The beginning of the feed list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-20241203-213022.png&#34; width=&#34;600&#34; height=&#34;1266&#34; alt=&#34;Auto-generated description: A screenshot of a social media app shows posts by Manton Reece discussing topics like media coverage, book covers, custom emojis, and Micro.blog features.&#34; class=&#34;block-center&#34;&gt;
&lt;figcaption&gt;The feed item list. Title-less posts are to be fully supported, with a bulk of the summary shown in the list. Titled posts should have a smaller summary.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The aesthetics are taking a bit of a back seat in favour of functionality for now; I haven&amp;rsquo;t even changed the default accent colour. But the infrastructure is there: tapping a feed will bring up the entries for that feed. This is using &lt;a href=&#34;https://docs.flutter.dev/cookbook/navigation/named-routes&#34;&gt;named routes&lt;/a&gt; for navigation, and &lt;a href=&#34;https://bloclibrary.dev/flutter-bloc-concepts/&#34;&gt;cubits&lt;/a&gt; for state management. It&amp;rsquo;s a bit more work upfront, but it does make for a neater codebase.&lt;/p&gt;
&lt;p&gt;The biggest challenge so far was getting the actual reader view working. I hoping to use the &lt;a href=&#34;https://pub.dev/packages/webview_flutter&#34;&gt;webview plugin&lt;/a&gt;, but when I tried adding it, I ran into a bunch of Gradle errors. These were either class version errors or dependency errors, depending on what I tried to fix it (I didn&amp;rsquo;t get screenshots, sorry). I eventually stumbled upon this &lt;a href=&#34;https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply&#34;&gt;Flutter Gradle plugin migration guide&lt;/a&gt;, and following this, along with upgrading Java to OpenJDK 25, got Gradle working again.&lt;/p&gt;
&lt;p&gt;But I was still getting build errors. The first couple were Gradle plugins  &lt;code&gt;com.android.application&lt;/code&gt; and &lt;code&gt;org.jetbrains.kotlin.android&lt;/code&gt; that needed to be updated. Easy stuff. And then I got this error message:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Execution failed for task &amp;#39;:webview_flutter_android:compileDebugJavaWithJavac&amp;#39;.
&amp;gt; Could not resolve all files for configuration &amp;#39;:webview_flutter_android:androidJdkImage&amp;#39;.
   &amp;gt; Failed to transform core-for-system-modules.jar to match attributes {artifactType=_internal_android_jdk_image, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
      &amp;gt; Execution failed for JdkImageTransform: /Users/leonmika/Library/Android/sdk/platforms/android-34/core-for-system-modules.jar.
         &amp;gt; Error while executing process /Users/leonmika/Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/jlink with arguments {--module-path /Users/leonmika/.gradle/caches/8.10.2/transforms/575ccd1a7426c0be21d9fe3a81898be3-05a021da-a1a7-409f-a30a-bba769b57371/transformed/output/temp/jmod --add-modules java.base --output /Users/leonmika/.gradle/caches/8.10.2/transforms/575ccd1a7426c0be21d9fe3a81898be3-05a021da-a1a7-409f-a30a-bba769b57371/transformed/output/jdkImage --disable-plugin system-modules}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running a web search on the error revealed &lt;a href=&#34;https://stackoverflow.com/a/79095064&#34;&gt;this Stack Overflow answer&lt;/a&gt;, which resolve it. There were still a few complaints about the the NDK version but after all that, the app finally launched with the web-viewer.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-20241203-212901.png&#34; width=&#34;600&#34; height=&#34;1266&#34; alt=&#34;Auto-generated description: A smartphone screen displays a simple app interface with the title Read The Feed and placeholder text saying Content goes here.&#34; class=&#34;block-center&#34;&gt;
&lt;figcaption&gt;The web-viewer, with a &#34;hello world&#34;-ish test HTML document&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I still need to actually render the entry, plus style the HTML a bit. The immediate goal after this, once the reader view, is getting this on my phone to start playing with it. It&amp;rsquo;s just barebones for now, but I find that the sooner I can start using a project myself, the more likely I am to keep at it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/30/oh-it-turns.html</link>
      <pubDate>Sat, 30 Nov 2024 06:31:53 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/30/oh-it-turns.html</guid>
      <description>&lt;p&gt;Oh, it turns out it&amp;rsquo;s an older style of referencing targets and is no longer supposed to be used. That&amp;rsquo;s a shame.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/3be220380c.png&#34; width=&#34;598&#34; height=&#34;62&#34; alt=&#34;A deprecated warning message indicates the need to replace the data-target attribute in a progress bar with an updated Stimulus.js format.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/26/upgraded-my-work.html</link>
      <pubDate>Tue, 26 Nov 2024 07:35:16 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/26/upgraded-my-work.html</guid>
      <description>&lt;p&gt;Upgraded my work laptop to Sequoia. &amp;ldquo;Love&amp;rdquo; the experience that this new version provides, especially the mouse-and-patience exercise I get in the morning. 👎&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&amp;ldquo;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2024/cleanshot-2024-11-26-at-07.30.252x.png%22&#34;&gt;https://cdn.uploads.micro.blog/25293/2024/cleanshot-2024-11-26-at-07.30.252x.png&amp;quot;&lt;/a&gt; width=&amp;ldquo;600&amp;rdquo; height=&amp;ldquo;541&amp;rdquo; alt=&amp;ldquo;Three permission requests stacked up, with the top one displayed asking if an app called &amp;ldquo;Obsidian&amp;rdquo; can find devices on local networks, with options to &amp;ldquo;Don&amp;rsquo;t Allow&amp;rdquo; or &amp;ldquo;Allow&amp;rdquo;.&amp;quot;&amp;gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/23/now-this-is.html</link>
      <pubDate>Sat, 23 Nov 2024 08:25:42 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/23/now-this-is.html</guid>
      <description>&lt;p&gt;Now this is cool: Hetzner has opened up a region in Singapore. The tyranny of distance is starting to abate.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/cleanshot-2024-11-23-at-08.22.22.png&#34; width=&#34;600&#34; height=&#34;250&#34; alt=&#34;Screenshot of Hetzner server location, showing Singapore as an option.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/16/now-that-my.html</link>
      <pubDate>Sat, 16 Nov 2024 07:40:11 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/16/now-that-my.html</guid>
      <description>&lt;p&gt;Now that my 1Password subdomain woes with Android Vivaldi &lt;a href=&#34;https://lmika.org/2024/09/03/some-followup-from.html&#34;&gt;has been tamed&lt;/a&gt;, it’s time to turn my attention to Safari:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/d674e05456.jpg&#34; width=&#34;600&#34; height=&#34;531&#34; alt=&#34;A password manager pop-up displays a list of saved password suggestions for the same domain, with no subdomains, 8 times.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/09/building-out-the.html</link>
      <pubDate>Sat, 09 Nov 2024 13:37:44 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/09/building-out-the.html</guid>
      <description>&lt;p&gt;Building out the meta elements of Cyber Burger, including the &amp;ldquo;menu du jour&amp;rdquo; a.k.a. the main menu. I&amp;rsquo;ve used food-service terms for the menu items to maintain the theme, but there is a button to switch them over to more conventional names should it be too unclear.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241109-132922.png&#34; width=&#34;600&#34; height=&#34;537&#34; alt=&#34;Auto-generated description: A retro-style game menu displays options like Start Shift and Employee Handbook under the title CYBER BURGER.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/11/02/title-design-this.html</link>
      <pubDate>Sat, 02 Nov 2024 10:39:26 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/11/02/title-design-this.html</guid>
      <description>&lt;p&gt;Title design this morning. Trying to get as close as I can to the &lt;a href=&#34;https://indieground.net/blog/80s-fonts-roundup&#34;&gt;Cyberspace Raceway font&lt;/a&gt; as my pixel art skills will allow for.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241102-103516.png&#34; width=&#34;600&#34; height=&#34;537&#34; alt=&#34;Auto-generated description: The map editor in Pico-8 depicting a retro-style screen displays CYBER BURGER, with the toolbox showing the sprites depicting the word CYBERUG and a pixelated burger icon.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/10/26/i-finished-my.html</link>
      <pubDate>Sat, 26 Oct 2024 14:19:23 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/10/26/i-finished-my.html</guid>
      <description>&lt;p&gt;I finished my experiment with htmgo, building the worlds most inefficient world clock. It uses HTMX swapping to get the time from the server every second.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241026-141456.png&#34; width=&#34;600&#34; height=&#34;465&#34; alt=&#34;Auto-generated description: A digital world clock showing the current time and date for UTC and Australia/Melbourne on October 26, 2024.&#34;&gt;
&lt;p&gt;It&amp;rsquo;s an interesting framework. Not sure it&amp;rsquo;s fully ready yet (you can&amp;rsquo;t change the bind port, for example) but might be useful in the future.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/10/20/i-was-poking.html</link>
      <pubDate>Sun, 20 Oct 2024 08:40:16 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/10/20/i-was-poking.html</guid>
      <description>&lt;p&gt;I was poking around Dave Winer’s &lt;a href=&#34;http://scripting.com/2014/07/16/myLatestSoftwareSnacks.html&#34;&gt;Software Snacks&lt;/a&gt; — a brilliant name for those — and I stumbled across Little Card Editor. Decided to give it a try.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/a89659d446.jpg&#34; width=&#34;600&#34; height=&#34;450&#34; alt=&#34;A cozy coffee table setup with a blue knitted item, blue headphones, and a smartphone displaying the time. The title ‘Morning Coffee Table’ is overlayed in the centre in a serif font.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/10/10/i-dont-use.html</link>
      <pubDate>Thu, 10 Oct 2024 08:05:12 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/10/10/i-dont-use.html</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t use Wordpress so this war between Matt Mullenweg and WP Engine is little more than #internet-drama to fuel my amusement. But Matt&amp;rsquo;s recent actions in this battle have started dragging users into the crossfire, and this is something I absolutely do not like. First by the blocking access to the plugin directory for those using WP Engine, and now by adding childish, your-with-me-or-agents-me UI elements on the wordpress.org login page:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-10-10-at-7.39.56am.png&#34; width=&#34;600&#34; height=&#34;535&#34; alt=&#34;The wordpress.org login page, with a username and password field, and a checkbox that says &#39;I am not affiliated with WP Engine in any way, financially or otherwise&#39;, circled with a red ellipsis annotation&#34;&gt;&lt;figcaption&gt;I had to see it for myself to believe it.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Granted, this looks to be a login form for plugins and forums, not actual blogs. But even so, Matt, is this necessary? You may have had a reason for going after WP Engine for some reason. I have no idea what those reasons are, and quite frankly, I no longer care. You start making changes to things in service of your war, I loose all respect for you.&lt;/p&gt;
&lt;p&gt;I may not use Wordpress, but I do use software that&amp;rsquo;s now owned by Automattic, like Pocketcasts, and seeing this makes me uneasy. What&amp;rsquo;s to say that these won&amp;rsquo;t be used in a similar way in the future?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Part of me wonders now whether this checkbox was added in jest. No evidence to support that apart from seeing various posts on Mastodon (I don&amp;rsquo;t have evidence to support that it wasn&amp;rsquo;t added in jest). If so, then I am a fool for taking the bait and getting worked up about this. It is an indication of how vicious this fight looks to me though, where adding such a checkbox would seem like a genuine escalation.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/10/06/ive-spent-the.html</link>
      <pubDate>Sun, 06 Oct 2024 11:14:31 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/10/06/ive-spent-the.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve spent the last week working on a small puzzle game called Coasters, where you presented with two images and a clue, and you need to guess the word or phrase. One puzzle a day, sort of like Wordle. I&amp;rsquo;ve got 10 puzzles ready to go and I may add more but no promises. &lt;a href=&#34;https://coasters.lmika.app&#34;&gt;Check it out if you like.&lt;/a&gt;&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20241006-110751.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;Auto-generated description: Two coasters are shown, one with the text &#39;happiness is attractive&#39; and the other displaying the logo &#39;crust bakery&#39;, with a prompt to guess a species of bird.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/08/22/i-enjoyed-reading.html</link>
      <pubDate>Thu, 22 Aug 2024 08:41:13 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/08/22/i-enjoyed-reading.html</guid>
      <description>&lt;p&gt;I enjoyed reading Kev Quirk&amp;rsquo;s post about &lt;a href=&#34;https://kevquirk.com/blog/building-a-simple-journal&#34;&gt;building a simple journal&lt;/a&gt;. I&amp;rsquo;m still using Day One, but I am still thinking of moving off it. So I was inspired to build a prototype similar to Kev&amp;rsquo;s, just to see if something similar works for me. Built using Go instead of PHP, but it also uses Simple CSS.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20240822-073836.png&#34; width=&#34;600&#34; height=&#34;481&#34; alt=&#34;Screenshot of a journal web-page with a text box with the contents saying &#39;Thanks, Kev, for the idea&#39;.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/07/26/reddits-decision-to.html</link>
      <pubDate>Fri, 26 Jul 2024 15:40:33 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/07/26/reddits-decision-to.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.404media.co/email/4650b997-7cc3-4578-834c-7e663ed3d516/&#34;&gt;Reddit&amp;rsquo;s decision&lt;/a&gt; to allow only Google to index their site will probably mean I&amp;rsquo;ll be seeing them far less often than I do — which is almost never anyway, and generally from the results of a search. So I&amp;rsquo;m recording this screenshot, which I call &amp;ldquo;Reddit in the results&amp;rdquo;, for posterity.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20240726-142742.png&#34; width=&#34;600&#34; height=&#34;408&#34; alt=&#34;A screenshot of an Ecosia search result for the query &#39;postgresql unsigned integer values&#39;, with links to Stack Overflow, Reddit, and PostgreSQL docs in the results&#34;&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Turns out Ecosia sources some of their index from Google, so these Reddit links will likely remain in my searches. I guess that makes this post unnecessary. I&amp;rsquo;m going to keep it up though, for posterity of my unnecessary effort to post for posterity. 😄&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/07/04/mark-the-date.html</link>
      <pubDate>Thu, 04 Jul 2024 09:51:26 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/07/04/mark-the-date.html</guid>
      <description>&lt;p&gt;Mark the date. First successful CI/CD run of a Go project running on my own Forgejo instance, running in Hetzner. 🙌&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/out-20240704-084448.png&#34; width=&#34;600&#34; height=&#34;461&#34; alt=&#34;Sreenshot of a successful Forgejo Runner result page, indicating that a project was checked out and tested with Go.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/06/06/speaking-of-bad.html</link>
      <pubDate>Thu, 06 Jun 2024 22:55:27 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/06/06/speaking-of-bad.html</guid>
      <description>&lt;p&gt;Speaking of bad UIs, volià: my first attempt at building something with &lt;a href=&#34;https://gioui.org&#34;&gt;Gio&lt;/a&gt;:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-06-06-at-9.39.41pm.png&#34; width=&#34;600&#34; height=&#34;478&#34; alt=&#34;A screenshot of a window with the title Gio, a connection header, a left pane showing a NATS message to send, and a right pane showing messages that can be received&#34;&gt;
&lt;p&gt;It doesn&amp;rsquo;t do anything now, but I&amp;rsquo;m hoping this will be something I can use to test &lt;a href=&#34;https://nats.io&#34;&gt;NATS&lt;/a&gt;. I will say Gio shows promise. Not a huge range of controls to use, but having everything run in a single memory address is nice.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/05/24/coding-standards-at.html</link>
      <pubDate>Fri, 24 May 2024 11:16:13 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/05/24/coding-standards-at.html</guid>
      <description>&lt;p&gt;Coding standards at work calls for US English in our codebase. So I&amp;rsquo;m typing words like &amp;ldquo;color,&amp;rdquo; &amp;ldquo;initialize,&amp;rdquo; and &amp;ldquo;data center.&amp;rdquo; And it pains me. I know that&amp;rsquo;s irrational but, you know, I never claimed to be rational when it comes to things like this.&lt;/p&gt;
&lt;p&gt;At least the spell-checker&amp;rsquo;s on my side.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-05-24-at-10.12.49am.png&#34; width=&#34;592&#34; height=&#34;174&#34; alt=&#34;Screen shot of this post showing red lines under the American spelling of &#39;color&#39;, &#39;initialize&#39; and &#39;data center&#39; indicating that they&#39;re miss-spelt&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/05/01/for-the-past.html</link>
      <pubDate>Wed, 01 May 2024 12:28:45 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/05/01/for-the-past.html</guid>
      <description>&lt;p&gt;For the last few years, I&amp;rsquo;ve been using 4/24 as the expiry date of test credit cards within Stripe. Well those days are literally in the past now.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-05-01-at-11.25.26am.png&#34; width=&#34;600&#34; height=&#34;431&#34; alt=&#34;Screenshot of a new credit card setup within Stripe showing the test credit card number of 4242 4242 4242 4242, and the expiry date 4/24, and the error message saying &#39;Your card&#39;s expiry date is in the past&#39;.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/04/27/interesting-to-see.html</link>
      <pubDate>Sat, 27 Apr 2024 10:05:10 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/04/27/interesting-to-see.html</guid>
      <description>&lt;p&gt;Interesting to see Google starting to solicit reviews for apps that came with the phone, such as the… Phone.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/6850a26e9f.png&#34; width=&#34;540&#34; height=&#34;600&#34; alt=&#34;Screenshot of a request for a review of the android Phone app, with five unfilled stars and a message saying that reviews will be public&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/04/16/love-the-new.html</link>
      <pubDate>Tue, 16 Apr 2024 09:36:27 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/04/16/love-the-new.html</guid>
      <description>&lt;p&gt;Love the new categories feature in Scribbles. Went back and added them to the posts on Coding Bits and Workpad. They look and feel great.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-04-16-at-8.33.53am.png&#34; width=&#34;541&#34; height=&#34;600&#34; alt=&#34;Screenshot of Scribbles post screen showing three posts, each with a different category with a different colour.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/04/16/took-a-while.html</link>
      <pubDate>Tue, 16 Apr 2024 09:13:54 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/04/16/took-a-while.html</guid>
      <description>&lt;p&gt;Took a while to troubleshoot why my shell script wasn&amp;rsquo;t running in Keyboard Maestro. Turns out I needed to add &lt;code&gt;#!/bin/zsh -l&lt;/code&gt; to launch it with ZSH, with the &lt;code&gt;-l&lt;/code&gt; switch to read my zprofile dot file.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&amp;ldquo;&lt;a href=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-04-16-at-8.10.49am.png%22&#34;&gt;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-04-16-at-8.10.49am.png&amp;quot;&lt;/a&gt; width=&amp;ldquo;600&amp;rdquo; height=&amp;ldquo;310&amp;rdquo; alt=&amp;ldquo;Screenshot of a Keyboard Maestro &amp;ldquo;run shellscript&amp;rdquo; step with the hash-bang line set to /bin/zsh with the -l switch&amp;rdquo;&amp;gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/03/13/blogroll-ported-to.html</link>
      <pubDate>Wed, 13 Mar 2024 22:27:02 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/03/13/blogroll-ported-to.html</guid>
      <description>&lt;p&gt;Blogroll ported to Micro.blog and placed in a sidebar on the post list screen using &lt;a href=&#34;https://tiny.micro.blog/microhooks/&#34;&gt;Tiny Theme Microhooks&lt;/a&gt;. I&amp;rsquo;ve yet to port the &lt;a href=&#34;https://lmika.org/blogroll&#34;&gt;Blogroll page&lt;/a&gt;, and may trim some of the recommendations appearing in the sidebar, but not bad for a first pass.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-03-13-at-10.21.31pm.png&#34; width=&#34;600&#34; height=&#34;443&#34; alt=&#34;Screenshot of lmika.org with the blogroll recommendations displayed as a sidebar&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Photo Bucket Update: Exporting To Zip</title>
      <link>https://lmika.org/2024/03/09/photo-bucket-update.html</link>
      <pubDate>Sat, 09 Mar 2024 10:33:57 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/03/09/photo-bucket-update.html</guid>
      <description>&lt;p&gt;Worked a little more on Photo Bucket this week. Added the ability to export the contents of an instance to a Zip file. This consist of both images and metadata.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-03-09-at-9.58.38-am.png&#34; width=&#34;600&#34; height=&#34;304&#34; alt=&#34;Screenshot of a finder window showing the contents of the exported Zip file&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve went with lines of JSON file for the image metadata. I considered a CSV file briefly, but for optional fields like captions and custom properties, I didn&amp;rsquo;t like the idea of a lot of empty columns. Better to go with a format that&amp;rsquo;s a little more flexible, even if it does mean more text per line.&lt;/p&gt;
&lt;p&gt;As for the images, I&amp;rsquo;m hoping the export to consist of the &amp;ldquo;best quality&amp;rdquo; version. What that means will depend on the instance. The idea is to have three tiers of image quality managed by the store: &amp;ldquo;original&amp;rdquo;, &amp;ldquo;web&amp;rdquo;, and &amp;ldquo;thumbnail&amp;rdquo;. The &amp;ldquo;original&amp;rdquo; version is the untouched version uploaded to the store. The &amp;ldquo;web&amp;rdquo; version is re-encoded from the &amp;ldquo;original&amp;rdquo; and will be slightly compressed with image metadata tags stripped out. The &amp;ldquo;thumbnail&amp;rdquo; version will be a small, highly compressed version suitable for the thumbnail. There is to be a decision algorithm in place to get an image given the desired quality level. For example, if something needed the &amp;ldquo;best quality&amp;rdquo; version of an image, and the &amp;ldquo;original&amp;rdquo; image is not available, the service will default to the &amp;ldquo;web&amp;rdquo; version (the idea is that some of these tiers will be optional depending on the need of the instances).&lt;/p&gt;
&lt;p&gt;This is all partially working at the moment, and I&amp;rsquo;m hoping to rework all this when I replace how stores and images relate to each other (This is what I&amp;rsquo;m starting on now, and why I built export now since this will be backwards incompatible). So for the moment the export simply consists of the &amp;ldquo;web&amp;rdquo; version.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got unit tests working for this as well. I&amp;rsquo;m trying a new approach for unit testing in this project. Instead of using mocks, the tests are actually running against a fully instantiated versions of the services. There exists a &lt;code&gt;servicestest&lt;/code&gt; package which does all the setup (using temporary directories, etc) and tear down of these services. Each individual unit test gets the services from this package and will run tests against a particular one.&lt;/p&gt;
&lt;p&gt;This does mean all the services are available and exercised within the tests, making them less like unit tests and more like integrations tests. But I think I prefer this approach. The fact that the dependent services are covered gives me greater confidence that they&amp;rsquo;re working. It also means I can move things around without changing mocks or touching the tests.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not to say that I&amp;rsquo;m not trying to keep each service their own component as much as I can. I&amp;rsquo;m still trying to follow best practice of component design: passing dependencies in explicitly when the services are created, for example. But setting them all up as a whole in the tests means I can exercise them while they serve the component being tested. And the dependencies are explicit anyway (i.e. no interfaces) so it makes sense keeping it that way for the tests as well. And it&amp;rsquo;s just easier anyway. 🤷&lt;/p&gt;
&lt;p&gt;Anyway, starting rework on images and stores now. Will talk more about this once it&amp;rsquo;s done.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Photo Bucket Update: More On Galleries</title>
      <link>https://lmika.org/2024/03/05/photo-bucket-update.html</link>
      <pubDate>Tue, 05 Mar 2024 22:43:05 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/03/05/photo-bucket-update.html</guid>
      <description>&lt;p&gt;Spent a bit more time working on Photo Bucket this last week&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, particularly around galleries. They&amp;rsquo;re progressing quite well. I&amp;rsquo;m made some strides in getting two big parts of the UI working now: adding and removing images to galleries, and re-ordering gallery items via drag and drop.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll talk about re-ordering first. This was when I had to bite the bullet and start coding up some JavaScript. Usually I&amp;rsquo;d turn to &lt;a href=&#34;https://stimulus.hotwired.dev&#34;&gt;Stimulus&lt;/a&gt; for this but I wanted to give &lt;a href=&#34;https://blog.jim-nielsen.com/2023/html-web-components/&#34;&gt;HTML web components&lt;/a&gt; a try. And so far, they&amp;rsquo;ve been working quite well.&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.blog/25293/2024/moving-gallery-images.mov&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The gallery page is generated server-side into the following HTML:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;main&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-imageset&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/galleries/1/items&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;image-grid&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;item-id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;7&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/photos/3&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/img/web/3&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;item-id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/photos/4&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/img/web/4&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;position&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;item-id&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/photos/1&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/_admin/img/web/1&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-image&lt;/span&gt;&amp;gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;pb-draggable-imageset&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;main&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each &lt;code&gt;&amp;lt;pb-draggable-image&amp;gt;&lt;/code&gt; node is a direct child of an &lt;code&gt;&amp;lt;pb-draggable-imageset&amp;gt;&lt;/code&gt;. The idea is that the user can rearrange any of the &lt;code&gt;&amp;lt;pb-draggable-image&amp;gt;&lt;/code&gt; elements within a single &lt;code&gt;&amp;lt;pb-draggable-imageset&amp;gt;&lt;/code&gt; amongst themselves. Once the user has moved an image onto to another one, the image will signal its new position by firing a custom event. The containing &lt;code&gt;&amp;lt;pb-draggable-imageset&amp;gt;&lt;/code&gt; element is listening to this event and will respond by actually repositioning the child element and sending a JSON message to the backend to perform the move in the database.&lt;/p&gt;
&lt;p&gt;A lot of this was based on the MDN documentation for &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API&#34;&gt;drag and drop&lt;/a&gt; and it follows the examples quite closely. I did find a few interesting things though. My first attempt at this was to put it onto the &lt;code&gt;&amp;lt;pb-draggable-image&amp;gt;&lt;/code&gt; element, but I wasn&amp;rsquo;t able to get any drop events when I did. Moving the &lt;code&gt;draggable&lt;/code&gt; attribute onto the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element seemed to work. I not quite sure why this is. Surely I can&amp;rsquo;t think of any reason as to why it wouldn&amp;rsquo;t work. It may had something else, such as how I was initialising the HTTP components.&lt;/p&gt;
&lt;p&gt;Speaking of HTML components, there was a time where the custom component&amp;rsquo;s &lt;code&gt;connectedCallback&lt;/code&gt; method was being called before the child &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; elements were present in the DOM. This was because I had the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag in the the HTML head and configured to be evaluated during parsing. Moving it to the end of the body and loading it as a module fixed that issue. Also I found that moving elements around using &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Element/before&#34;&gt;element.before&lt;/a&gt; and &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Element/after&#34;&gt;element.after&lt;/a&gt; would actually call &lt;code&gt;connectedCallback&lt;/code&gt; and &lt;code&gt;disconnectedCallback&lt;/code&gt; each time, meaning that any event listeners registered within &lt;code&gt;connectedCallback&lt;/code&gt; would need to be de-registered, otherwise events would be handled multiple times. This book-keeping was slightly annoying, but it worked.&lt;/p&gt;
&lt;p&gt;Finally, there was moving the items with the database. I&amp;rsquo;m not sure how best to handle this, but I have that method that seems to work. What I&amp;rsquo;m doing is tracking the position of each &amp;ldquo;gallery item&amp;rdquo; using a &lt;code&gt;position&lt;/code&gt; field. This field would be 1 for the first item, 2 for the next, and so on for each item in the gallery. The result of fetching items would just order using this field, so as long as they&amp;rsquo;re distinct, they don&amp;rsquo;t need to be a sequence incrementing by 1, but I wanted to keep this as much as possible.&lt;/p&gt;
&lt;p&gt;The actual move involves two update queries. The first one will update the positions of all the items that are to shift left or right by one to &amp;ldquo;fill the gap&amp;rdquo;. The way it does this is that when an item is moved from position X to position Y, the value of &lt;code&gt;position&lt;/code&gt; between X and Y would be changed by +1 if X &amp;gt; Y, or by –1 if Y &amp;gt; X. This is effectively the same as setting position X to X + 1, and so on, but done using one &lt;code&gt;UPDATE&lt;/code&gt; statement. The second query just sets the position of item X to Y.&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s moving gallery items. I&amp;rsquo;m not sure how confident I am with this approach, but I&amp;rsquo;ve been testing this, both manually and by writing unit tests. It&amp;rsquo;s not quite perfect yet: I&amp;rsquo;m still finding bugs (I found some while coming up with these screencasts). Hopefully, I&amp;rsquo;ll be able to get to the bottom of them soon.&lt;/p&gt;
&lt;p&gt;The second bit of work was to actually add and remove images in the gallery themselves. This, for the moment, is done using a &amp;ldquo;gallery picker&amp;rdquo; which is available in the image details. Clicking &amp;ldquo;Gallery&amp;rdquo; while viewing an image will show the list of galleries in the system, with toggles on the left. The galleries an image already belongs to is enabled, and the user can choose the galleries they want the image to be in by switching the toggles on and off. These translate to &lt;code&gt;inserts&lt;/code&gt; and &lt;code&gt;remove&lt;/code&gt; statements behind the scenes.&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://lmika.org/uploads/2024/adding-removing-images-to-galleries.mov&#34; controls=&#34;controls&#34; preload=&#34;metadata&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The toggles are essentially just HTML and CSS, and a bulk of the code was &lt;a href=&#34;https://www.w3schools.com/howto/howto_css_switch.asp&#34;&gt;taken from this example&lt;/a&gt;, with some tweaks. They look good, but I think I may need to make them slightly smaller for mouse and keyboard.&lt;/p&gt;
&lt;p&gt;I do see some downside with this interaction. First, it reverses the traditional idea of adding images to a gallery: instead of doing that, your selecting galleries for an image. I&amp;rsquo;m not sure if this would be confusing for others (it is modelled on how Google Photos works). Plus, there&amp;rsquo;s no real way to add images in bulk. Might be that I&amp;rsquo;ll need to add a way to select images from the &amp;ldquo;Photos&amp;rdquo; section and have a dialog like this to add or remove them all from a gallery. I think this would go far in solving both of these issues.&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s where things are. Not sure what I&amp;rsquo;ll work on next, but it may actually be import and export, and the only reason for this is that I screwed up the base model and will need to make some breaking changes to the DB schema. And I want to have a version of export that&amp;rsquo;s compatible with the original schema that I can deploy to the one and only production instance of Photo Bucket so that I can port the images and captions over to the new schema. More on this in the future, I&amp;rsquo;m sure.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Apparently I&amp;rsquo;m more than happy to discuss work in progress, yet when it comes to talking about something I&amp;rsquo;ve finished, I freeze up. 🤷&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/03/04/signed-up-as.html</link>
      <pubDate>Mon, 04 Mar 2024 07:02:05 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/03/04/signed-up-as.html</guid>
      <description>&lt;p&gt;Signed up as a lifetime member to Scribbles. Given how fun it is to use, it was an easy decision. Fantastic work, Vincent.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/6f3829ab30.jpg&#34; width=&#34;600&#34; height=&#34;550&#34; alt=&#34;Screenshot of Scribbles billing screen with a ‘Lifetime member’ payment tier and thank you message.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2024/02/12/spent-some-time.html</link>
      <pubDate>Mon, 12 Feb 2024 22:25:51 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2024/02/12/spent-some-time.html</guid>
      <description>&lt;p&gt;Spent some time this evening working on my image hosting tool. It&amp;rsquo;s slowly coming along, but wow do I suck at UI design (the &amp;ldquo;Edit Photo&amp;rdquo; screen needs some rebalancing).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-02-12-at-10.24.08-pm.png&#34; width=&#34;600&#34; height=&#34;450&#34; alt=&#34;Screenshot of a browser showing an image admin section with a grid of images&#34;&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2024/screenshot-2024-02-12-at-10.24.16-pm.png&#34; width=&#34;600&#34; height=&#34;450&#34; alt=&#34;Screenshot of a browser showing a single image form with an image on the right and two text fields on the left&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Test Creek: A Test Story With Evergreen.ink</title>
      <link>https://lmika.org/2023/12/07/test-creek-a.html</link>
      <pubDate>Thu, 07 Dec 2023 22:34:57 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/12/07/test-creek-a.html</guid>
      <description>&lt;p&gt;Had a play with &lt;a href=&#34;https://jonathanhays.me/2023/12/06/evergreen-ink/&#34;&gt;Evergreen.ink&lt;/a&gt; this afternoon. It was pretty fun. Made myself a test story called &lt;a href=&#34;https://testcreek.lmika.dev/&#34;&gt;Test Creek&lt;/a&gt; which you can try out (the story was written by me but all the images were done using DALL-E).&lt;/p&gt;
&lt;p&gt;The experience was quite intuitive. I&amp;rsquo;ve yet to try out the advanced features, like the Sapling scripting engine, but the basics are really approachable for anyone not interested with any of that.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-of-evergreen-ink.png&#34; width=&#34;600&#34; height=&#34;394&#34; alt=&#34;A screenshot of the Evergreen.ink editor, showing the contents of a card with two options and a preview on the right&#34;&gt;
&lt;p&gt;I would recommend not writing too much on a single card. Keep it to maybe two or three paragraphs. Otherwise the text will start to flow over the image, like it does on one of the cards in this story. Evergreen.ink does keep the text legible with a translucent background. But still, it&amp;rsquo;s just too much text.&lt;/p&gt;
&lt;p&gt;I should also say that the preview, to the right of the editor, is interactive, meaning that you can use it to jump to cards backed by the options. While I was playing around, I was wondering why there wasn&amp;rsquo;t a quick way to do this. It wasn&amp;rsquo;t until I started writing this post that I actually tried the option in the preview, and it worked.&lt;/p&gt;
&lt;p&gt;As for the app itself, if I could make one improvement, it would be something like an image picker which would allow me to reuse images already attached to other cards. I&amp;rsquo;m not sure how best to use images in these types of stories, but the way I was going for was more to accent the story instead of simply illustrating what&amp;rsquo;s going on in the prose. So I wanted to reuse images over a series of related cards, and in order to do that I had to upload duplicates.&lt;/p&gt;
&lt;p&gt;But really, this is quite a minor quibble. On the whole I was quite impress by how well the experience was. It&amp;rsquo;s not easy trying to express something as complex as an interactive story, and I think Evergreen.ink did a pretty decent job.&lt;/p&gt;
&lt;p&gt;So yeah, give it a try. It was quite fun putting this interactive story together. I haven&amp;rsquo;t got any other ideas lined up, but it would be good to make another one down the line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; One other thing to be aware of is that the link given to you when you try to share a story requires a login. So if you want to avoid that, you&amp;rsquo;ll need to choose the Zip option, which basically bundles the story as a static website. You can deploy it to Netlify quite easily (just check the permissions of the files first, I had to &lt;code&gt;chmod 666&lt;/code&gt; them).  Thank-you &lt;a href=&#34;https://rknight.me/&#34;&gt;Robb&lt;/a&gt; for letting me know.&lt;/p&gt;
&lt;p&gt;Also, thank-you to omg.lol for the Switchboard feature. It saved my tale dealing with the new redirect.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Idea For Mainboard Mayhem: A Remote Pickup</title>
      <link>https://lmika.org/2023/11/19/sort-of-inbetween.html</link>
      <pubDate>Sun, 19 Nov 2023 21:21:56 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/11/19/sort-of-inbetween.html</guid>
      <description>&lt;p&gt;Sort of in-between projects at the moment so I&amp;rsquo;m doing a bit of light stuff on Mainboard Mayhem. I had an idea for a new element: a remote control which, when picked up, will allow the player to toggle walls and tanks using the keyboard, much like the green and blue buttons.&lt;/p&gt;
&lt;p&gt;I used ChatGGT to come up with some artwork, and it produced something that was pretty decent.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/red-pixel-button-generated-by-dalle.png&#34; width=&#34;600&#34; height=&#34;600&#34; alt=&#34;Image generated from DALL-E with the prompt: pixel art of a remote control with a single red button styled like the tiles found in Chips Challange, rotated 45 degrees to the right.&#34;&gt;
&lt;figcaption&gt;Prompt: pixel art of a remote control with a single red button styled like the tiles found in Chips Challange, rotated 45 degrees to the right.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Only issue was that the image was huge — 1024 x 1024 — and the tiles in Mainboard Mayhem were only 32 x 32.&lt;/p&gt;
&lt;p&gt;I tried shrinking it down in Acorn, using various scaling algorithms. The closest that worked was bringing it down slowly to about 128 x 128 using Nearest Neighbour, than trying to go all the way down  to 32 x 32 using Lanczos. That worked, but it required true 32 bit colour to be recognisable, and I wanted to preserve the 16 colour palette used by the original Chips Challenge.&lt;/p&gt;
&lt;p&gt;So using the original image as a reference, I bit the bullet and drew my own in Acorn. You can see it here in this test level:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/mainboard-mayhem-example-levem.png&#34; width=&#34;600&#34; height=&#34;315&#34; alt=&#34;Example Mainboard Mayhem level showing the green and blue remote controls. The controls have 4 small buttons and one large bulbous button that is either blue or green, with a bit of phong and shadow applied&#34;&gt;
&lt;figcaption&gt;They&#39;re the elements that look like remote controls.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It turn out okay. At least it&amp;rsquo;s recognisable. Anyway, I coded it up and gave it a bit of a try:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;https://cdn.uploads.micro.blog/25293/2023/mainboard-mayhem-demo.mov&#34; poster=&#34;https://lmika.org/uploads/2023/mainboard-mayhem-demo-poster.png&#34; controls=&#34;controls&#34; playsinline=&#34;playsinline&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Yeah, it works well. When the player has the appropriate colour remote, they can hit either &lt;kbd&gt;Z&lt;/kbd&gt; or &lt;kbd&gt;X&lt;/kbd&gt; to toggle the green walls or blue tanks respectively. I really should add some indicators in the status bar to show which button to press.&lt;/p&gt;
&lt;p&gt;Not sure what I&amp;rsquo;ll do after this. The fun part was coming up with the element. But I guess I&amp;rsquo;ll have to come up with a few puzzles that use it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/10/15/more-work-on.html</link>
      <pubDate>Sun, 15 Oct 2023 16:52:50 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/10/15/more-work-on.html</guid>
      <description>&lt;p&gt;More work on &lt;a href=&#34;https://mainboard.lmika.dev&#34;&gt;Mainboard Mayhem&lt;/a&gt; today.  Had a bit more success getting the Windows build into a releasable state.&lt;/p&gt;
&lt;p&gt;First thing was the app icon. &lt;a href=&#34;https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/&#34;&gt;That blog post&lt;/a&gt; I talked about yesterday worked: I was able to set the icon of the executable. I did make a slight adjustment though. The post suggested using ImageMagick to produce the ICO file, but I wasn&amp;rsquo;t happy with how they looked. There were a lot of artefacts on the smaller icon sizes.&lt;/p&gt;
&lt;p&gt;So I looked around for an alternative, and found &lt;a href=&#34;https://github.com/leaanthony/winicon&#34;&gt;this package&lt;/a&gt; by Lea Anthony. He&amp;rsquo;s the maintainer of &lt;a href=&#34;https://wails.app&#34;&gt;Wails&lt;/a&gt;, a cross-platform toolkit for making browser-based GUI apps in Go, sort of like Electron but without bundling Chrome. In fact, most of the build for Mainboard Mayhem was put together by reading the Wails source code, so I trust he knows what his doing. And sure enough, his package produced a nicely scaled ICO file from a source PNG image. Better yet, it was distributed as a Go package, so I could no need to install and shell-out to run it: I could just integrated it directly into the project&amp;rsquo;s build tool.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&#34;github.com/akavel/rsrc&#34;&gt;rsrc&lt;/a&gt; to generate the SYSO file with the icon worked as expected: Go did pick it up and embed it into the executable. I did have some trouble getting the Go compiler to pick up these files at first. In short, they need to be in the same directory as the &lt;code&gt;main&lt;/code&gt; package. So if you&amp;rsquo;re running &lt;code&gt;go build ./cmd/thing&lt;/code&gt;, make sure the SYSO files are in &lt;code&gt;./cmd/thing&lt;/code&gt;. Other than that, no real issues here.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-15-at-10.43.28-am.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Screenshot of Windows 10 file browser with mainboard.exe shown with the app icon, plus a few sdl DLLs&#34;&gt;
&lt;figcaption&gt;A beautiful site: Mainboard.exe with the embedded app icon&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;One last thing I had to deal with was the console window. Running a Go app in Windows shows the console by default. Perfectly fine for command line tools, but less so for games:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-15-at-11.10.32-am.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Screenshot of Mainboard Mayhem running with the console window open in the background showing log messages&#34;&gt;
&lt;figcaption&gt;Mainboard Mayhem with that annoying console window. Even the log messages are dull (well, unless you&#39;re working on the app).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So I had to find a way to hide the console on launch. Since Mainboard Mayhem is using &lt;a href=&#34;http://libsdl.org&#34;&gt;SDL&lt;/a&gt;, I&amp;rsquo;m actually using &lt;a href=&#34;https://www.mingw-w64.org&#34;&gt;MinGW&lt;/a&gt; to cross-compile the Windows release on an Ubuntu build runner. The documentation for MinGW suggests adding &lt;code&gt;-mwindows&lt;/code&gt; as a linker option to hide the console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# What I was doing before, which didn&#39;t work
CGO_ENABLED=1 \
CC=&amp;quot;x86_64-w64-mingw32-gcc&amp;quot; \
GOOS=&amp;quot;windows&amp;quot; \
CGO_LDFLAGS=&amp;quot;-mwindows -L…&amp;quot; \
go build -o dist/cclm/mainboard.exe ./cmd/cclm&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This didn&amp;rsquo;t actually work when I tried it: launching the app kept bringing up the console. Turns out what I should&amp;rsquo;ve done was follow the advice of many &lt;a href=&#34;https://stackoverflow.com/a/36728885/1505851&#34;&gt;Stack Overflow&lt;/a&gt; answers, and set &lt;code&gt;-ldflags &amp;quot;-H=windowsgui&amp;quot;&lt;/code&gt; on the Go command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This works
CGO_ENABLED=1 \
CC=&amp;quot;x86_64-w64-mingw32-gcc&amp;quot; \
GOOS=&amp;quot;windows&amp;quot; \
CGO_LDFLAGS=&amp;quot;-L…&amp;quot; \
go build -ldflags &amp;quot;-H=windowsgui&amp;quot; -o dist/cclm/mainboard.exe ./cmd/cclm&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works even without the &lt;code&gt;-mwindows&lt;/code&gt; switch. Not completely sure why though. I guess MinGW is not actually being used for linking? Or maybe &lt;code&gt;-m&lt;/code&gt; only works with C header files? Don&amp;rsquo;t know. 🤷 But doesn&amp;rsquo;t matter: the console no longer shows up on launch.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-15-at-10.44.06-am.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Screenshot of Mainboard Mayhem running, but with no console window. File browser running in the background&#34;&gt;
&lt;figcaption&gt;Mainboard Mayhem without the console window. A much nicer experience now.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, there was testing it all, and for this I just bit the bullet and set-up a Windows 10 virtual machine in Azure. The rate is something like $0.16 AUD an hour, an easy decision compared to spending time trying to get a VM with Windows 10 running on my machine.&lt;/p&gt;
&lt;p&gt;One remaining thing that&amp;rsquo;s slightly annoying is Windows Defender refusing to launch it after download, doing effectively the same thing as Gatekeeper on MacOS does:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-15-at-10.43.38-am.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Screenshot of Windows Defender SmartScreen indicating that it&#39;s refusing to start an unrecognised app. A single button saying &#39;Don&#39;t Run&#39; appears at the bottom of the dialog.&#34;&gt;
&lt;figcaption&gt;Gatekeeper a.la. Microsoft.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m sure there&amp;rsquo;s a way around it but it&amp;rsquo;s probably not worth learning about it at this stage. It&amp;rsquo;s easy enough to dismiss: click &amp;ldquo;More Info&amp;rdquo; and the click &amp;ldquo;Run Anyway&amp;rdquo;:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-15-at-10.43.43-am.png&#34; width=&#34;600&#34; height=&#34;407&#34; alt=&#34;Screenshot of Windows Defender SmartScreen indicating that it&#39;s refusing to start an unrecognised app, saying the name of the executable and that the publisher is unknown. Two buttons saying &#39;Run Anyway&#39; and &#39;Don&#39;t Run&#39; appears at the bottom of the dialog.&#34;&gt;
&lt;figcaption&gt;Clicking &#39;More Info&#39; gives you a way to launch the app.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But other than that, I think the Windows version of Mainboard Mayhem is ready. I&amp;rsquo;ve updated the website to include the Windows archive if anyone&amp;rsquo;s interested.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/10/14/golang-weekly-had.html</link>
      <pubDate>Sat, 14 Oct 2023 22:20:04 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/10/14/golang-weekly-had.html</guid>
      <description>&lt;p&gt;Spent some time today on Mainboard Mayhem, trying to finish the Windows build. I&amp;rsquo;ve actually got Windows version of the game being built for a while now. I just haven&amp;rsquo;t published them, mainly because I haven&amp;rsquo;t got the app icon set-up yet.&lt;/p&gt;
&lt;p&gt;But this week, &lt;a href=&#34;https://golangweekly.com/issues/479&#34;&gt;Golang Weekly&lt;/a&gt; had a link to &lt;a href=&#34;https://hjr265.me/blog/adding-icons-for-go-built-windows-executable/&#34;&gt;a blog post by Mahmud Ridwan&lt;/a&gt; on how to do so. It looked pretty straightforward, so I thought I&amp;rsquo;d give it a try.&lt;/p&gt;
&lt;p&gt;And yeah, the instructions themselves were easy enough, and I wish I could say if they worked or not. But in order to test it, I need a Windows machine. And I don&amp;rsquo;t have one, and I wasn&amp;rsquo;t about to get one just for this.&lt;/p&gt;
&lt;p&gt;So I tried setting up Windows in a VM using &lt;a href=&#34;getutm.app&#34;&gt;UTM&lt;/a&gt;. I got this far:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-10-14-at-3.58.00-pm.png&#34; width=&#34;600&#34; height=&#34;484&#34; alt=&#34;A blue Windows install screen within a MacOS window showing a spinner and the message &#39;Just a moment…&#39; underneath&#34;&gt;
&lt;p&gt;Yeah, this infinite spinner has been staring at me pretty much all day. I got a Windows 10 installer ISO using &lt;a href=&#34;https://github.com/TuringSoftware/CrystalFetch&#34;&gt;CrystalFetch&lt;/a&gt;, and it  &lt;em&gt;seemed&lt;/em&gt; to work. But it just doesn&amp;rsquo;t want to boot up for the first time.&lt;/p&gt;
&lt;p&gt;Not actually sure what the problem is. The error message seems to suggest that it&amp;rsquo;s having trouble connecting to the internet. Might be that? Or maybe the installation didn&amp;rsquo;t complete properly? Could be anything. 🤷&lt;/p&gt;
&lt;p&gt;So no luck getting this tested yet. I&amp;rsquo;m wondering if it might be easier to forget virtualisation and just launch a Windows instance in the cloud somewhere instead.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/10/13/installed-the-latest.html</link>
      <pubDate>Fri, 13 Oct 2023 21:19:38 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/10/13/installed-the-latest.html</guid>
      <description>&lt;p&gt;Installed the latest version of Android today. One obvious change: the calculator app displays fractions in the result. And there&amp;rsquo;s no way to turn it off.&lt;/p&gt;
&lt;p&gt;This… doesn&amp;rsquo;t appeal to me. Not enough for me to change apps, at least not yet. But I wish there was a setting to change it back to decimals.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-20231013-210744.png&#34; width=&#34;576&#34; height=&#34;600&#34; alt=&#34;The built-in calculator showing a result of 2.5 in decimals, and 2 1/2 in gray underneath.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/09/17/spent-some-time.html</link>
      <pubDate>Sun, 17 Sep 2023 17:10:13 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/09/17/spent-some-time.html</guid>
      <description>&lt;p&gt;Spent some time today building a site for my Go utility packages. A feature I&amp;rsquo;ve decided to add is a &lt;a href=&#34;https://gopkgs.lmika.dev/tools/templates/&#34;&gt;Go template playground&lt;/a&gt;, where you can test out Go templates in the browser. Not something I&amp;rsquo;ll use everyday but I&amp;rsquo;ve occasionally wished for something like this before. Could be useful.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-09-17-at-4.02.35-pm.png&#34; width=&#34;600&#34; height=&#34;474&#34; alt=&#34;The template playground, with a field for the template saying &#39;hello what&#39;, the data which has &#39;what&#39; equal to &#39;world&#39; in Json, and the output which is &#39;Hello, world&#39;&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Mainboard Mayhem</title>
      <link>https://lmika.org/2023/08/30/project-update-on.html</link>
      <pubDate>Wed, 30 Aug 2023 23:27:50 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/08/30/project-update-on.html</guid>
      <description>&lt;p&gt;Project update on Mainboard Mayhem, my Chip&amp;rsquo;s Challenge fan game. I didn&amp;rsquo;t get it finished in time for the release deadline, which was last weekend. I blame work for that. We&amp;rsquo;re going through a bit of a crunch at the moment, and there was a need to work on the weekend.&lt;/p&gt;
&lt;p&gt;The good news is that there wasn&amp;rsquo;t much left to do, and after a few more evenings, I&amp;rsquo;m please to say that it&amp;rsquo;s done. The game is finish, and ready for release.&lt;/p&gt;
&lt;p&gt;So here it is: &lt;a href=&#34;https://mainboard.lmika.dev&#34;&gt;Mainboard Mayhem: A Chip&amp;rsquo;s Challenge fan game&lt;/a&gt; (and yes, that&amp;rsquo;s its full title).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Screenshot of Mainboard Mayhem&#34;&gt;
&lt;p&gt;At the moment it&amp;rsquo;s only available for MacOS. It should work on both Intel and Apple Silicon Macs, although I&amp;rsquo;ve only tested on my M2 Mac Mini running Ventura.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s good to finally see this project done. It&amp;rsquo;s been in development for about last ten years, and I spent half of that time wondering whether it was worth getting it finished it at all. Not committing to anything meant any work I did do on it was pretty aimless, and I always felt like I was wasting my time. Giving myself three weeks to either kill it, or release it helped a lot. I&amp;rsquo;ll start making deadlines for all the other unfinished projects I&amp;rsquo;m working on.&lt;/p&gt;
&lt;p&gt;As to what that next project will be, I not sure at this stage. Part of me wants to wait until this crunch time ends, but I suspect I&amp;rsquo;ll get antsy before then and start work on something else. I&amp;rsquo;ll keep you posted one way or the other.&lt;/p&gt;
&lt;p&gt;But for now, if you happen to give it a try, thank you and I hope you enjoy it.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/avatar.png&#34; width=&#34;100&#34; height=&#34;100&#34; alt=&#34;The app icon of Mainboard Mayhem&#34; class=&#34;block-center&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/08/20/project-update-for.html</link>
      <pubDate>Sun, 20 Aug 2023 12:00:37 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/08/20/project-update-for.html</guid>
      <description>&lt;p&gt;Project update for Mainboard Madness. Well, today&amp;rsquo;s the deadline for getting the thing code complete, and what a surprised, it&amp;rsquo;s not finished.&lt;/p&gt;
&lt;p&gt;To be fair, it&amp;rsquo;s pretty close. All the levels are more or less done, and the beats of the in-game lore have been added. It all just needs tightening up a little. I spent today working on the end-game phase, which mainly involved coding up the credit sequence, and making sure I include credits for those involved in the original game (and who&amp;rsquo;s artwork I lifted).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/mainboard-mayhem-credit-sequence.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Mainboard mayhem credit sequence showing the final credit message &#39;Thanks for playing&#39;&#34;&gt;
&lt;p&gt;The work remaining is to finish one or two game elements, adding a proper app icon, and finishing off the website. I&amp;rsquo;m wondering whether to add sound, but I feel bad enough taking the artwork from the original game, I rather not take the sound effects as well. That will mean the game will remain silent for the time being, but I can probably live with that for now.&lt;/p&gt;
&lt;p&gt;I think we&amp;rsquo;re still on track for getting this finished by this time next week. Last dash to the finish line, then I can put this 9 year project to rest for a while.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/08/05/working-on-my.html</link>
      <pubDate>Sat, 05 Aug 2023 14:43:19 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/08/05/working-on-my.html</guid>
      <description>&lt;p&gt;Working on my Chips Challenge &amp;ldquo;fan game&amp;rdquo; this morning. Added the notion of &amp;ldquo;lower thirds,&amp;rdquo; which will show text at the bottom of the play field. I&amp;rsquo;m hoping to use it for narrative or way-finding, like here in this hub level:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-08-05-at-1.38.39-pm.png&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Demonstration of the lower third indicating the direction of movement towards tutorial levels in a hub map&#34;&gt;
&lt;p&gt;Also working on puzzle design. There&amp;rsquo;s about 19 or so &amp;ldquo;real&amp;rdquo; puzzles but I&amp;rsquo;m wondering if it&amp;rsquo;s worth adding a few tutorial ones for those that have never played the original Chip Challenge before. I&amp;rsquo;ve done about 5 such puzzles and I think I need to add maybe 3 or 4 more to cover everything I&amp;rsquo;m hoping to demonstrate. I wish I liked puzzle design more than I like tinkering on the engine.&lt;/p&gt;
&lt;p&gt;Of course, the big question is why I&amp;rsquo;m working on this at all. There is, for lack of a better word, a vision for this, in terms of narrative and structure, but this project has been in development on and off for about 9 years or so, and I&amp;rsquo;m wondering if it&amp;rsquo;s time to just stop working on it altogether. I really am starting to get sick of it, in a way. And yet, this project has shown remarkable staying power over that time that I feel like if I don&amp;rsquo;t actually wrap it up, it&amp;rsquo;ll just continued to be worked on. It feels like the only way to end this project is to finish it, in one way or another.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;ll set myself a dead-line: something releasable in two weeks, and actually released a week after that. After that, no more! I&amp;rsquo;ll work on something else.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/07/29/attempting-app-icon.html</link>
      <pubDate>Sat, 29 Jul 2023 11:28:48 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/07/29/attempting-app-icon.html</guid>
      <description>&lt;p&gt;Attempting to design an app icon for a Chips Challenge fan game I&amp;rsquo;m working on. Going for something that looks like the fireball sprite in the original game with a hint more realism and tinted in the colour blue. For reference, here&amp;rsquo;s the original fireball sprite:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-07-29-at-10.13.14-am.png&#34; width=&#34;160&#34; height=&#34;160&#34; alt=&#34;Fireball sprite in Chips Challenge&#34;&gt;
&lt;p&gt;And here&amp;rsquo;s my attempt:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/blue-plasma.png&#34; width=&#34;384&#34; height=&#34;384&#34; alt=&#34;Blue plasma shaped like the fireball sprite in Chips Challenge&#34;&gt;
&lt;p&gt;I started with Stable Diffusion to get the base image:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/unknown.jpg&#34; width=&#34;384&#34; height=&#34;384&#34; alt=&#34;Stable Diffusion production of the image described in the caption&#34;&gt;
&lt;figcaption&gt;Prompt: a blue plasma fireball shaped like a throwing star with four points on a white background, pixel art&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Then imported into Acorn to rotate it, colourise it, and distort it to look a bit closer to the original sprite.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-07-29-at-10.16.09-am.png&#34; width=&#34;600&#34; height=&#34;478&#34; alt=&#34;Screenshot of Acorn showing the image produced by Stable Diffusion rotated and the effects applied to produced the final image.&#34;&gt;
&lt;p&gt;Desaturating the original image got rid of the purple centre, then applying the Glowhoo and Hue Adjust effect recolourised it to the blue I was looking for (I&amp;rsquo;m not sure what the Glowhoo effect does, but it seems to adjust the colour based on the pixel intensity, so it was good enough for what I wanted). Finally, I added a Twirl Distortion effect to achieve the slight warp in the star.&lt;/p&gt;
&lt;p&gt;And yeah, it&amp;rsquo;s not going to win any design awards, but it&amp;rsquo;s good enough for now.&lt;/p&gt;
&lt;p&gt;Oh, and just for kicks, here was my first attempt of producing the sprite using Affinity Designer.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/plasma.png&#34; width=&#34;400&#34; height=&#34;400&#34; alt=&#34;First attempt at the blue plasma logo, crafted within Affinity Designer&#34;&gt;
&lt;p&gt;That&amp;rsquo;s &lt;em&gt;definitely&lt;/em&gt; not going to win any design awards. 😂&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/07/25/for-reasons-that.html</link>
      <pubDate>Tue, 25 Jul 2023 09:55:24 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/07/25/for-reasons-that.html</guid>
      <description>&lt;p&gt;For reasons that are &amp;ldquo;totally&amp;rdquo; coincidental to the &lt;a href=&#34;https://arstechnica.com/tech-policy/2023/07/musk-rushes-out-new-twitter-logo-its-just-an-x-that-someone-tweeted-at-him/&#34;&gt;news of the day&lt;/a&gt;, I had a quick check to see how much the domain Y would cost:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screen-shot-2023-07-25-at-8.48.27-am.png&#34; width=&#34;600&#34; height=&#34;419&#34; alt=&#34;Screenshot of Porkbun prices for Y.co. Domains Y.inc for $3057.15, Y.xyz for $54583.41, and Y.pro for $2729.65 are available.&#34;&gt;
&lt;p&gt;Hmm, might be a bit much for a joke domain. 😃&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/07/22/a-really-nice.html</link>
      <pubDate>Sat, 22 Jul 2023 10:34:52 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/07/22/a-really-nice.html</guid>
      <description>&lt;p&gt;A really nice quality of life improvement you can make in Terminal.app: map &lt;kbd&gt;Option&lt;/kbd&gt;-&lt;kbd&gt;Backspace&lt;/kbd&gt;  to &lt;code&gt;^W&lt;/code&gt; (Control-W, or &lt;code&gt;\027&lt;/code&gt;) so that pressing it in the shell will delete one word to the left, like most other MacOS apps:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-07-22-at-9.31.33-am.png&#34; width=&#34;600&#34; height=&#34;553&#34; alt=&#34;Key mapping preference pane within Terminal, with a new key mapping with the delete key and option modifier sends control W as text&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/07/01/til-you-can.html</link>
      <pubDate>Sat, 01 Jul 2023 11:51:41 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/07/01/til-you-can.html</guid>
      <description>&lt;p&gt;TIL you can enter a photo description in Google Photos. Select a photo, click the Info icon, and a free-text &amp;ldquo;description&amp;rdquo; field is revealed. Not super sure what the description is to be used for, but I&amp;rsquo;m hoping to use it for photo captions.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/screenshot-2023-07-01-at-10.52.27-am.png&#34; width=&#34;600&#34; height=&#34;389&#34; alt=&#34;Screenshot of Google Photos in Safari with the info plane reveal and a sample description entered just above the photo metadata. The sample description reads: This is a description. I guess it can be used as a caption if you want it to.&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/05/21/ive-been-working.html</link>
      <pubDate>Sun, 21 May 2023 22:52:03 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/05/21/ive-been-working.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been working on Micropub Checkins over the last week. It&amp;rsquo;s been a bit of a rush trying to get it into a usable state for an upcoming trip. And by &amp;ldquo;usable&amp;rdquo;, I mean a form that I can tolerate, and when it comes to projects like this, I can tolerate quite a lot. It can have a really dodgy UI (which this does) and miss some really important features that are annoying to work around; but if it works, and doesn&amp;rsquo;t loose data, I&amp;rsquo;ll be fine with it.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1977e67e66.jpg&#34; width=&#34;200&#34; height=&#34;422&#34; alt=&#34;The main screen showing three check-ins&#34; /&gt;
&lt;figcaption&gt;The main screen showing the recent check-ins. Note the lock next to some of them. These won&#39;t be published until the locks are removed.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The last week was dedicated to making the act of checking in distinct from publishing it. Until now, check-ins were published the minute they were entered, meaning that you cannot check-in somewhere unless you&amp;rsquo;re comfortable with people knowing where you are the minute you do. Yes, some people like it that way, but not me. And I&amp;rsquo;m aware that this&amp;rsquo;ll only be the case if people are following my check-in blog, which I&amp;rsquo;m doubtful of.&lt;/p&gt;
&lt;p&gt;So pressing the floating action button and choosing a check-in type now starts the flow of a new check-in that will get saved in an SQLite database. You can edit the check-in whenever you like, so long as it&amp;rsquo;s not published. Currently there&amp;rsquo;s no real way of deleting a check-in unless it&amp;rsquo;s been published. This is a bit dodgy, but it&amp;rsquo;s a good example of how tolerant I am with working around these feature gaps for the moment.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/c083b6b602.jpg&#34; width=&#34;200&#34; height=&#34;422&#34; alt=&#34;The new styled edit screen with a title, description, rating, and a ready for check-in switch&#34; /&gt;
&lt;figcaption&gt;The newly styled edit screen. Notice the rating field, which will appear for eateries.&lt;/figcpation&gt;
&lt;/figure&gt;
&lt;p&gt;Check-ins can be published by tapping the upward facing button on the main screen. Any check-in with a lock is private and will not be published until you toggle the &amp;ldquo;Ready to publish&amp;rdquo; switch in the properties. Doing so will not change the date of the check-in: it will still have the date and time that check-in was created.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/41ce8e540e.jpg&#34; width=&#34;200&#34; height=&#34;422&#34; alt=&#34;The target list screen, showing two targets: one test target, and one for my real check-in blog&#34; /&gt;
&lt;figcaption&gt;The targets are currently hard-coded but they can be turned on or off. I had a bit of trouble publishing a check-in to two targets, so I&#39;m not sure if I&#39;ll keep multi-target publishing.
&lt;/figure&gt;
&lt;p&gt;On the subject of publishing, I had some issues with Dart&amp;rsquo;s date and time methods. The method on the DateTime class used to produce an ISO-8501 date-stamp don&amp;rsquo;t include the time-zone if the date and time is not in UTC. This is important as I want the post date and time to be as close to the check-in time as possible, and in the time-zone of the phone. DateTime knows all this, including what the time-zone we&amp;rsquo;re in. So why didn&amp;rsquo;t the developers include it in the ISO-8501 date-time string?&lt;/p&gt;
&lt;p&gt;This is really strange. Fortunately, ChatGPT stepped in to help out, writing a function which will add the time-zone offset to the ISO-8501 date-time string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String formatTimeZoneOffset(Duration offset) {
  String sign = offset.isNegative ? &#39;-&#39; : &#39;+&#39;;
  int hours = offset.inHours.abs();
  int minutes = (offset.inMinutes.abs() % 60);

  return &#39;$sign${_padZero(hours)}:${_padZero(minutes)}&#39;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Honestly, ChatGPT has been so helpful over the past week with this project, I probably should give it a credit if I get this polished enough to release.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/05/16/i-also-got.html</link>
      <pubDate>Tue, 16 May 2023 17:29:46 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/05/16/i-also-got.html</guid>
      <description>&lt;p&gt;I got a little bored today so I added task progress indicators to this Obsidian roadmap thing I built for work.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/f0a731a687.png&#34; width=&#34;300&#34; height=&#34;119&#34; alt=&#34;Screenshot of coloured rectangles next to a link with the text &#39;View In Jira&#39; and below the heading &#39;Jira Tickets&#39;&#34;&gt;
&lt;figcaption&gt;What the task progress indicators look like.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When a task is created, but is not yet scheduled, it appears as an outlined rectangle. It turns into a grey rectangle when it&amp;rsquo;s added to the sprint. It then changes colour as the task progresses through the software lifecycle; turning purple while it&amp;rsquo;s being developed, blue while it&amp;rsquo;s being tested, and finally green when it&amp;rsquo;s ready for release.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re implemented as embedded SVG images, added directly to the note much like the span element used for status labels.&lt;/p&gt;
&lt;figure&gt; 
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/b6d68c6ffc.png&#34; width=&#34;600&#34; height=&#34;216&#34; alt=&#34;Screenshot of the markup of the embedded SVG image next to the &#39;View In Jira&#39; link&#34; /&gt;
&lt;figcaption&gt;Move the insertion point over the the SVG image to edit the markup.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It&amp;rsquo;s nice being able to add embellishments like this. Obsidian treating notes as regular files on the file-system is a huge advantage for these sorts of automations. No need to learn how to make a plugin; just write a shell script&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that&amp;rsquo;ll output Markdown, schedule it to run a couple of times a day, and you&amp;rsquo;re good to go.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;The &amp;ldquo;shell script&amp;rdquo; is actually written in Go, using the really useful &lt;a href=&#34;https://github.com/bitfield/script&#34;&gt;script&lt;/a&gt; package to simplify all the pipelining stuff.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/05/13/back-working-on.html</link>
      <pubDate>Sat, 13 May 2023 13:25:17 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/05/13/back-working-on.html</guid>
      <description>&lt;p&gt;Back working on Micropub Checkin.  Re-engineered the home page to now include a list of what would eventually be check-ins — both historical and soon to be published — complete with the check-in type emoji as the icon:&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/843ca0bc72.jpg&#34; width=&#34;200&#34; height=&#34;444.33&#34; alt=&#34;Main screen for Micropub Checkin&#34; /&gt;
&lt;figcaption&gt;Main screen for Micropub Checkin&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The same list of emoji icons now adorn the check-in type picker as well (except for the airplane one which seems to always be shown as what I can only describe as the “Wingding” representation):&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/941486fd3a.jpg&#34; width=&#34;200&#34; height=&#34;444.33&#34; alt=&#34;The check-in type picker&#34; /&gt;
&lt;figcaption&gt;The check-in type picker&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I went around a bit trying to work out how best to use these emojis icons in the &lt;code&gt;leading&lt;/code&gt; slot of the &lt;code&gt;ListTile&lt;/code&gt; widget. I expored trying to convert them to &lt;code&gt;IconData&lt;/code&gt;, but it turns out just using a &lt;code&gt;Text&lt;/code&gt; widget with a large font worked well. I wrapped in in a &lt;code&gt;Widget&lt;/code&gt; type with a fixed font-size and so far it looks quite good, at least in the emulator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EmojiIcon extends StatelessWidget {
  final String emoji;

  const EmojiIcon({super.key, required this.emoji});

  Widget build(BuildContext context) {
    return Text(emoji, style: TextStyle(fontSize: 26.0));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also started working on a &lt;a href=&#34;https://pub.dev/documentation/bloc/latest/bloc/Cubit-class.html&#34;&gt;Cubit&lt;/a&gt; to handle state for the main page. I had a bit of trouble working ont where the soon-to-be database call to get the list of checkins should go in the cubit. After asking ChatGPT, it looks like the initializer is the best place for it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CheckinListCubit extends Cubit&amp;lt;CheckinListState&amp;gt; {

  CheckinListCubit(): super(LoadingCheckinListState()) {
    loadCheckinList();
  }

  void loadCheckinList() async {
    var listOfCheckins = await read_database(); 
    emit(FoundCheckinListState(checkins));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve got some scaffolding code in place to simulate this, and so far it seems to work.&lt;/p&gt;
&lt;p&gt;I need to start working on the database layer and having the ability to edit and delete check-ins before they&amp;rsquo;re published. I think I&amp;rsquo;ll tackle that next.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/05/13/does-this-loading.html</link>
      <pubDate>Sat, 13 May 2023 11:00:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/05/13/does-this-loading.html</guid>
      <description>&lt;p&gt;Does this loading window really need to be modal?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1ae3c787e9.png&#34; width=&#34;600&#34; height=&#34;529&#34; alt=&#34;The Android SDK Component Installer window showing installation of an emulator image at 47% through the download&#34;&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/04/30/hmm-either-daniel.html</link>
      <pubDate>Sun, 30 Apr 2023 15:17:15 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/04/30/hmm-either-daniel.html</guid>
      <description>&lt;p&gt;Hmm, either Daniel is super obsessed with the Beths (or at least posting about it), or there&amp;rsquo;s a bug somewhere. 😀&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/43f2a73591.jpg&#34; width=&#34;276&#34; height=&#34;600&#34; alt=&#34;Multiple entries in the Micro.blog timeline of danielpunkass post about the Beths being his new obsession.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Building F5 To Run</title>
      <link>https://lmika.org/2023/04/16/building-f-to.html</link>
      <pubDate>Sun, 16 Apr 2023 11:36:18 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/04/16/building-f-to.html</guid>
      <description>&lt;p&gt;At the risk of talking about something that I&amp;rsquo;ve only just started, I&amp;rsquo;d thought today I write about what I&amp;rsquo;m working on right now.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been going through my digital archives this weekend, trying to get it into something more permenant than the portable USB drives it&amp;rsquo;s currently stored on. Amongst all that stuff is a bunch of QBasic apps and games I wrote way back when I was a kid. Over the years it&amp;rsquo;s laid dormant but I do like revising them from time to time.&lt;/p&gt;
&lt;p&gt;Is it a form of nostalgia? An attempts to live past glories? Maybe? I was pretty proud of them at the time, much like anyone else that&amp;rsquo;s proud of their early stuff while they&amp;rsquo;re leaning to code. And I know about the risk of living in the past at the expense of the present. But I also know that if I get rid of them, I&amp;rsquo;d regret it. I already regret loosing the things from the archive so far, due to bad disks or just missing things while copying them from portable hard-drive to portable hard-drive. I don&amp;rsquo;t want to loose any more.&lt;/p&gt;
&lt;p&gt;So in an act of posterity, I&amp;rsquo;d figured it&amp;rsquo;s time to coat them in amber and put them online. So that&amp;rsquo;s what I&amp;rsquo;m doing now.&lt;/p&gt;
&lt;p&gt;These apps run without issue in DosBox, and hearing about how the Wayback Machine has managed to make a bunch of DOS games playable within the browser, I wondered if I could do something similar. Anything that deals with virtualisation is always going to be a little bit involved. I guess one thing going for these is that they were written for a pretty slow machine and a pretty thin OS that would be trivial for modern hardware to emulate. The apps themselves, even compiled to an EXE file, are not very taxing on the hardware back then either. But I still expected to do a bit of heavy lifting myself.&lt;/p&gt;
&lt;p&gt;How wrong I was! After a tiny bit of research — and by tiny I mean one &lt;a href=&#34;https://www.ecosia.org/&#34;&gt;Ecosia&lt;/a&gt; search — I managed to find a JavaScript library called &lt;a href=&#34;https://js-dos.com/&#34;&gt;JS-Dos&lt;/a&gt; which provides a DosBox emulator that&amp;rsquo;s runnable from a browser. All I need to do is prepare a bundle on what I want to run (more on that below) and with a bit of JavaScript, I can start a DosBox machine in the browser and mount it to a HTML element. The library does all the work.&lt;/p&gt;
&lt;h2 id=&#34;how-to-use-js-dos&#34;&gt;How To Use JS-Dos&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s still early days, but here&amp;rsquo;s what I learnt about using the library so far.&lt;/p&gt;
&lt;p&gt;First, the library comes as a NPM package, or can be loaded from their CDN in the form of a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; import.
I first tried using the NPM package, but I didn&amp;rsquo;t know the appropriate &lt;code&gt;import&lt;/code&gt; statement to use, and the documentation was not forthcoming on this front.&lt;/p&gt;
&lt;p&gt;So I went with the CDN approach. I&amp;rsquo;m using &lt;a href=&#34;https://gohugo.io/hugo-pipes/&#34;&gt;Hugo Pipes&lt;/a&gt; to fetch the remote JavaScript file and make a local bundle so I can host it from the site itself. It comes with some CSS which I also need to get (note, I&amp;rsquo;m using parenthesis instead of curly braces here as I&amp;rsquo;m not sure how to include two curly braces in a code-block).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(( $jsDosCSS := resources.GetRemote &amp;quot;https://js-dos.com/v7/build/releases/latest/js-dos/js-dos.css&amp;quot; ))
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;(( $jsDosCSS.RelPermalink ))&amp;quot;&amp;gt;
        
(( $jsDosJS := resources.GetRemote &amp;quot;https://js-dos.com/v7/build/releases/latest/js-dos/js-dos.js&amp;quot; ))
&amp;lt;script src=&amp;quot;(( $jsDosJS.RelPermalink ))&amp;quot; defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also needed to get an appropriate wdosbox emulator. This comes in the form of a WASM file, plus a small JavaScript file which
I assume is some simple bootstrapper.  I&amp;rsquo;ve downloaded these and stored them in the &lt;code&gt;static/emulators&lt;/code&gt; directory of my Hugo project.
The JSDos library loads them on demand and I needed to set the URL path prefix for these two files so that JSDos knows where to get them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;emulators.pathPrefix = &#39;/emulators/&#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I needed to build a bundle. These are the DOS programs that are launched with DosBox. They&amp;rsquo;re effectively just Zip files holding some metadata, the DOS executable, and any files needed for the program to run.  There&amp;rsquo;s some &lt;a href=&#34;https://js-dos.com/v7/build/docs/jsdos-bundle&#34;&gt;basic information&lt;/a&gt; about how to make them, and there&amp;rsquo;s &lt;a href=&#34;https://dos.zone/studio&#34;&gt;even an online tool&lt;/a&gt; which will take on a lot of the tedious work. I&amp;rsquo;ve used it to make a couple of test bundles and it works quite well. I&amp;rsquo;d like to eventually make my bundles myself but I&amp;rsquo;ll stick with the tool for the time being, at least until I&amp;rsquo;ve got a DosBox configuration that I&amp;rsquo;m happy with. One thing the tool does is give you the ability to define an overlay so that these DOS apps are usable from within a mobile browsers. I&amp;rsquo;ll see if I can get away from needing these overlays at this stage. I&amp;rsquo;m not expecting anyone with a mobile app to try these out.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;The contents of .jsdos/dosbox.conf for the test bundle `logo-2.jsdos`&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;[sdl]
autolock=false

fullscreen=false
fulldouble=false
fullresolution=original
windowresolution=original
output=surface
sensitivity=100
waitonerror=true
priority=higher,normal
mapperfile=mapper-jsdos.map
usescancodes=true
vsync=false
[dosbox]
machine=svga_s3

language=
captures=capture
memsize=16
[cpu]
core=auto
cputype=auto
cycles=max

cycleup=10
cycledown=20
[mixer]
nosound=false
rate=44100

blocksize=1024
prebuffer=20

[render]
# frameskip: How many frames DOSBox skips before drawing one.
#    aspect: Do aspect correction, if your output method doesn&#39;t support scaling this can slow things down!.
#    scaler: Scaler used to enlarge/enhance low resolution modes.
#              If &#39;forced&#39; is appended, then the scaler will be used even if the result might not be desired.
#            Possible values: none, normal2x, normal3x, advmame2x, advmame3x, advinterp2x, advinterp3x, hq2x, hq3x, 2xsai, super2xsai, supereagle, tv2x, tv3x, rgb2x, rgb3x, scan2x, scan3x.

frameskip=0
aspect=false
scaler=none

[midi]
#     mpu401: Type of MPU-401 to emulate.
#             Possible values: intelligent, uart, none.
# mididevice: Device that will receive the MIDI data from MPU-401.
#             Possible values: default, win32, alsa, oss, coreaudio, coremidi, none.
# midiconfig: Special configuration options for the device driver. This is usually the id of the device you want to use.
#               See the README/Manual for more details.

mpu401=intelligent
mididevice=default
midiconfig=

[sblaster]
#  sbtype: Type of Soundblaster to emulate. gb is Gameblaster.
#          Possible values: sb1, sb2, sbpro1, sbpro2, sb16, gb, none.
#  sbbase: The IO address of the soundblaster.
#          Possible values: 220, 240, 260, 280, 2a0, 2c0, 2e0, 300.
#     irq: The IRQ number of the soundblaster.
#          Possible values: 7, 5, 3, 9, 10, 11, 12.
#     dma: The DMA number of the soundblaster.
#          Possible values: 1, 5, 0, 3, 6, 7.
#    hdma: The High DMA number of the soundblaster.
#          Possible values: 1, 5, 0, 3, 6, 7.
# sbmixer: Allow the soundblaster mixer to modify the DOSBox mixer.
# oplmode: Type of OPL emulation. On &#39;auto&#39; the mode is determined by sblaster type. All OPL modes are Adlib-compatible, except for &#39;cms&#39;.
#          Possible values: auto, cms, opl2, dualopl2, opl3, none.
#  oplemu: Provider for the OPL emulation. compat might provide better quality (see oplrate as well).
#          Possible values: default, compat, fast.
# oplrate: Sample rate of OPL music emulation. Use 49716 for highest quality (set the mixer rate accordingly).
#          Possible values: 44100, 49716, 48000, 32000, 22050, 16000, 11025, 8000.

sbtype=sb16
sbbase=220
irq=7
dma=1
hdma=5
sbmixer=true
oplmode=auto
oplemu=default
oplrate=44100

[gus]
#      gus: Enable the Gravis Ultrasound emulation.
#  gusrate: Sample rate of Ultrasound emulation.
#           Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
#  gusbase: The IO base address of the Gravis Ultrasound.
#           Possible values: 240, 220, 260, 280, 2a0, 2c0, 2e0, 300.
#   gusirq: The IRQ number of the Gravis Ultrasound.
#           Possible values: 5, 3, 7, 9, 10, 11, 12.
#   gusdma: The DMA channel of the Gravis Ultrasound.
#           Possible values: 3, 0, 1, 5, 6, 7.
# ultradir: Path to Ultrasound directory. In this directory
#           there should be a MIDI directory that contains
#           the patch files for GUS playback. Patch sets used
#           with Timidity should work fine.

gus=false
gusrate=44100
gusbase=240
gusirq=5
gusdma=3
ultradir=C:\ULTRASND

[speaker]
# pcspeaker: Enable PC-Speaker emulation.
#    pcrate: Sample rate of the PC-Speaker sound generation.
#            Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
#     tandy: Enable Tandy Sound System emulation. For &#39;auto&#39;, emulation is present only if machine is set to &#39;tandy&#39;.
#            Possible values: auto, on, off.
# tandyrate: Sample rate of the Tandy 3-Voice generation.
#            Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
#    disney: Enable Disney Sound Source emulation. (Covox Voice Master and Speech Thing compatible).

pcspeaker=true
pcrate=44100
tandy=auto
tandyrate=44100
disney=true

[joystick]
# joysticktype: Type of joystick to emulate: auto (default), none,
#               2axis (supports two joysticks),
#               4axis (supports one joystick, first joystick used),
#               4axis_2 (supports one joystick, second joystick used),
#               fcs (Thrustmaster), ch (CH Flightstick).
#               none disables joystick emulation.
#               auto chooses emulation depending on real joystick(s).
#               (Remember to reset dosbox&#39;s mapperfile if you saved it earlier)
#               Possible values: auto, 2axis, 4axis, 4axis_2, fcs, ch, none.
#        timed: enable timed intervals for axis. Experiment with this option, if your joystick drifts (away).
#     autofire: continuously fires as long as you keep the button pressed.
#       swap34: swap the 3rd and the 4th axis. can be useful for certain joysticks.
#   buttonwrap: enable button wrapping at the number of emulated buttons.

joysticktype=auto
timed=true
autofire=false
swap34=false
buttonwrap=false

[serial]
# serial1: set type of device connected to com port.
#          Can be disabled, dummy, modem, nullmodem, directserial.
#          Additional parameters must be in the same line in the form of
#          parameter:value. Parameter for all types is irq (optional).
#          for directserial: realport (required), rxdelay (optional).
#                           (realport:COM1 realport:ttyS0).
#          for modem: listenport (optional).
#          for nullmodem: server, rxdelay, txdelay, telnet, usedtr,
#                         transparent, port, inhsocket (all optional).
#          Example: serial1=modem listenport:5000
#          Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial2: see serial1
#          Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial3: see serial1
#          Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial4: see serial1
#          Possible values: dummy, disabled, modem, nullmodem, directserial.

serial1=dummy
serial2=dummy
serial3=disabled
serial4=disabled

[dos]
#            xms: Enable XMS support.
#            ems: Enable EMS support.
#            umb: Enable UMB support.
# keyboardlayout: Language code of the keyboard layout (or none).

xms=true
ems=true
umb=true
keyboardlayout=auto

[ipx]
# ipx: Enable ipx over UDP/IP emulation.

ipx=true
[autoexec]
echo off
mount c .
c:

type jsdos~1/readme.txt
echo on

LOGO.EXE
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;I&amp;rsquo;m keeping the bundles in the &lt;code&gt;static/bundles&lt;/code&gt; directory, which sits alongside the emulator WASM file. They&amp;rsquo;re not huge binaries but I&amp;rsquo;m still using &lt;code&gt;git lfs&lt;/code&gt; to manage them. Best to keep the the Git repository relatively sane.&lt;/p&gt;
&lt;p&gt;Finally, it&amp;rsquo;s just a matter of adding some JavaScript to start DosBox, load the bundle, and mount it onto a HTML element:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Dos(document.querySelector(&amp;quot;#element-to-mount&amp;quot;)).run(&amp;quot;/bundles/bundle-to-load.jsdos&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&amp;rsquo;s pretty much it.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/e6318284ee.jpg&#34; alt=&#34;Safari window with a test webpage with JSDos running an editor. The editor has the line &#39;This is the running Dos program.&#39;&#34;/&gt;
  &lt;figcaption&gt;A test webpage with JSDos running Logo 2, which is one of my Basic programs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After a few hours, I&amp;rsquo;ve managed to get a test version of this working. There are a few things that need working on: the app I&amp;rsquo;m trying require the Alt key, which is not readily available of Apple keyboards, so I may need to do something about that (JSDos comes with a virtual keyboard with Ctrl and Alt so it&amp;rsquo;s not a complete show-stopper)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. And I&amp;rsquo;ll need to get styling and more maintainable JavaScript written for this (I&amp;rsquo;m using &lt;a href=&#34;https://stimulus.hotwired.dev&#34;&gt;StimulusJS&lt;/a&gt; for the JavaScript&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;).  But I&amp;rsquo;m impressed by how well this works, given the minimal amount of effort from my part.  Shoulders of giants and all that.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I&amp;rsquo;ve since learnt that Alt is bound to the Option key in MacOS.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;One thing I leant about Hugo is that it&amp;rsquo;s bundled with &lt;a href=&#34;https://esbuild.github.io&#34;&gt;ESBuild&lt;/a&gt;, meaning that it&amp;rsquo;s resource pipeline supports NPM packages.  My understanding is that this is invokable using the &lt;a href=&#34;https://gohugo.io/hugo-pipes/js/&#34;&gt;js.Build&lt;/a&gt; construct.  This is super useful to know.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/04/02/a-nice-addition.html</link>
      <pubDate>Sun, 02 Apr 2023 10:17:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/04/02/a-nice-addition.html</guid>
      <description>&lt;p&gt;What would be a nice addition to the spell-check suggestions menu is a brief (3-5 words) definition of the word. I always find myself choosing the wrong suggestion, and a feature like this would help a lot.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/31c25356d7.jpg&#34; width=&#34;600&#34; height=&#34;240&#34; alt=&#34;Concept screenshot of the spell-checker menu with definitions&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Updating Bocce Scorecard</title>
      <link>https://lmika.org/2023/03/26/updating-bocce-scorecard.html</link>
      <pubDate>Sun, 26 Mar 2023 11:13:43 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/26/updating-bocce-scorecard.html</guid>
      <description>&lt;p&gt;I didn&amp;rsquo;t get to a lot of side-project work this week, but I did have to make a large change to a project we use to track scores for our &amp;ldquo;bocce club&amp;rdquo;. So I&amp;rsquo;d though I&amp;rsquo;d say a few words about that today.&lt;/p&gt;
&lt;p&gt;We had our bocce &amp;ldquo;grand final&amp;rdquo; a few weeks ago, and one of the matches resulted in a tie between two players. Unfortunately, the Bocce Scorecard web-app I build could not properly handle these, which meant that I had to fix it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be honest in saying that that this was never really well fleshed out in the code, and there was actually a small bug which didn&amp;rsquo;t handle the ranking of players well. But I was pushing to keep this app as the de-facto source of truth for these matches, and there was a bit riding on this being correct (there&amp;rsquo;s a $4 trophy involved). So I had to get this fix before our next match, which was today.&lt;/p&gt;
&lt;p&gt;Now, I been having trouble coming up with a good description of what the rules should be so I&amp;rsquo;d figured a simple example would suffice.&lt;/p&gt;
&lt;p&gt;Imagine that there are four players: Tom, Dick, Harry, and Sally. They play several bocce matches during a season — which roughly corresponds to one calendar year — plus three &amp;ldquo;grand final&amp;rdquo; matches at the end. Each player would be awarded a number of &amp;ldquo;season points&amp;rdquo; (we informally call them &amp;ldquo;cookies&amp;rdquo;) based on how well they did in the match. The person with the most season points at the end of the last grand final match wins the season, and gets to take home the trophy.&lt;/p&gt;
&lt;p&gt;In regular matches, the wining player is awarded one season point, while the remaining players get nothing:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Player&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Score&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Season Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Tom&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;11&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Dick&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;8&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Sally&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;6&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Harry&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;3&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In grand final matches, the winning player is awarded 5 points, the one coming in second gets 2, and the one coming in third gets 1:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Player&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Score&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Season Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Tom&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;11&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Dick&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;8&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Sally&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;6&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Harry&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;3&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Season points for grand final matches are distributed this way so that a single grand final match is roughly equivalent to an entire season of regular matches. This means that if someone is coming in last during the regular season (fun fact: that person&amp;rsquo;s usually me) they still has a chance to win the trophy if they do well during the grand final.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s say that our four players are playing a grand final match, and Dick and Sally tie for second place. What should happen is that both Dick and Sally should be awarded half the season points they would get for both the second and third rank, given that they are evenly match for these two positions. In other words, they should both get 1.5 season points (1 + 2 = 3 / 2 = 1.5).
Harry, who came last, still gets zero.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Player&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Score&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Season Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Tom&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;11&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Dick&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;7&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Sally&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;7&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;Harry&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;3&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This was the rule that I needed to change.&lt;/p&gt;
&lt;p&gt;What I found when I started working on this is that the rule definitions themselves needed to be closer to how the players are ranked. What &lt;a href=&#34;https://workingset.net/2022/01/13/pgwc-scoring-rules.html&#34;&gt;was previously done&lt;/a&gt; was that the players were sorted based on their match score, and then the rules were applied to each one by checking the win condition and awarding the points if they match it.  But this didn&amp;rsquo;t fit nicely with this new approach to ties.&lt;/p&gt;
&lt;p&gt;So instead of the conditions and awards approach, I simplified the rule definitions such that it simply defines the number of season points based on the players rank. This effectively makes it a simple map between rank and points. For normal matches the mapping would look like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Rank&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Season Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;and for grand final matches, like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Rank&lt;/th&gt;
&lt;th style=&#34;text-align:left&#34;&gt;Season Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;2&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left&#34;&gt;3&lt;/td&gt;
&lt;td style=&#34;text-align:left&#34;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now, when a match is over, the logic that awards the season points first sorts the players based on their match score, and then groups the players into buckets such that all the players with same match score are lumped together in the same bucket.  Ranks are then assigned to the players in descending score order. If two players have the same score, they will be given two ranks (e.g. Dick and Sally would have both rank two and three). Finally, season points are awarded with the rule definition and the following formula:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;season_points(player) = sum[over player_ranks](rules.rank_scores[rank]) / no_of_players_in_bucket
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This new logic works for ties between any number of players with any ranks.&lt;/p&gt;
&lt;p&gt;But the introduction of division now means that the season points can be a decimal, and the database row that holds the season points is an integer type. I didn&amp;rsquo;t want to make it a floating point, so I took a page from Stripe and simply changed the representation of the season scores such that 1 season point is represented as 100 in the database. This is exposed in the rules configuration, which now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;rank_scores&amp;quot;: [
    {
      &amp;quot;points&amp;quot;: 500,
      &amp;quot;rank&amp;quot;: 1
    },
    {
      &amp;quot;points&amp;quot;: 200,
      &amp;quot;rank&amp;quot;: 2
    },
    {
      &amp;quot;points&amp;quot;: 100,
      &amp;quot;rank&amp;quot;: 3
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;although all the non-admin screens properly represents the score as a decimal number.&lt;/p&gt;
&lt;p&gt;I managed to get all finished and pushed to the server, but there was one other thing I think I&amp;rsquo;d like to get done down the line. My friends have been asking me about the outcome of previous seasons recently and I&amp;rsquo;d like to make it easier for them to view it themselves.  The data exists, but it&amp;rsquo;s super hacky to get: you need to &amp;ldquo;open&amp;rdquo; a previous season so that the leader board is shown on the home page, then close it again once the info is seen. This can only be done by the admin user (i.e. me) and the screens to do it leave a lot to be desired:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/57d0145835.jpg&#34; alt=&#34;Screenshot of Bocce Scorecard showing the admin section for seasons&#34;/&gt;
  &lt;figcaption&gt;The current season admin section.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;What I&amp;rsquo;m thinking is adding a &amp;ldquo;Seasons&amp;rdquo; section in the web-app.  Clicking &amp;ldquo;Seasons&amp;rdquo; in the nav will bring up the following screen:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/7b48eade55.jpg&#34; alt=&#34;Mockup of the new end user season browser section&#34;/&gt;
  &lt;figcaption&gt;Mockup of a new season browser section.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The game variant will appear the top as a tab, and below them are all the current and past seasons arranged in descending chronological order.  Clicking the &lt;code&gt;&amp;gt;&lt;/code&gt; will bring up the season results display:&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1722969ad6.jpg&#34; alt=&#34;Mockup of the details of a season&#34;&gt;
  &lt;figcaption&gt;Drilling down into a season brings up the details, complete with a leader board and list of matches played during that season.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This will show the final outcome of the season, any metadata associated with the season, and the matches of the season, along with the winner. Clicking the location will bring up the particular bocce session so that all the matches played that day can be seen.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll see when I get around to building this. It&amp;rsquo;s actually been a while since I&amp;rsquo;ve last touched this project while making such a large feature.&lt;/p&gt;
&lt;p&gt;Oh, and since it&amp;rsquo;s been a while, this usually means I needed to upgrade &lt;a href=&#34;https://gobuffalo.io&#34;&gt;Buffalo&lt;/a&gt;, the framework this app is using. Doing this usually means that you&amp;rsquo;ll need to change your app in some way to handle the new build process. This time, it&amp;rsquo;s moving the &lt;code&gt;main.go&lt;/code&gt; file, previously in the project directory, into a &lt;code&gt;cmd/app&lt;/code&gt; directory.  When you see output like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;leonmika@Stark bocce-scorecard % buffalo build -o /tmp/app
Usage:
  buffalo build [flags]

Aliases:
  build, b, bill, install

Flags:
      --build-flags strings        Additional comma-separated build flags to feed to go build
      --clean-assets               will delete public/assets before calling webpack
      --dry-run                    runs the build &#39;dry&#39;
      --environment string         set the environment for the binary (default &amp;quot;development&amp;quot;)
  -e, --extract-assets             extract the assets and put them in a distinct archive
  -h, --help                       help for build
      --ldflags string             set any ldflags to be passed to the go build
      --mod string                 -mod flag for go build
  -o, --output string              set the name of the binary
  -k, --skip-assets                skip running webpack and building assets
      --skip-build-deps            skip building dependencies
      --skip-template-validation   skip validating templates
  -s, --static                     build a static binary using  --ldflags &#39;-linkmode external -extldflags &amp;quot;-static&amp;quot;&#39;
  -t, --tags string                compile with specific build tags
  -v, --verbose                    print debugging information

ERRO[0000] Error: open cmd/app/main.go: no such file or directory 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;rsquo;ll need to create a &lt;code&gt;cmd/app&lt;/code&gt; directory and move &lt;code&gt;main.go&lt;/code&gt; into the &lt;code&gt;cmd/app&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;This will get the build working again but it will break &lt;code&gt;buffalo dev&lt;/code&gt; as it could no longer find the main file in the project directory. To fix &lt;em&gt;that&lt;/em&gt;, you&amp;rsquo;ll need to open up &lt;code&gt;.buffalo.dev.yml&lt;/code&gt; and add the following property:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build_target_path: &amp;quot;./cmd/app&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will get the dev build working again.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know why the dev command honours this config, yet the build command chooses to look at a hard coded path. Wouldn&amp;rsquo;t it have been easier to express this in a single configuration file?&lt;/p&gt;
&lt;p&gt;And let&amp;rsquo;s not leave Node out of the cold. If you&amp;rsquo;re trying to run &lt;code&gt;buffalo build&lt;/code&gt; and you&amp;rsquo;re getting this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#21 12.21 node:internal/crypto/hash:71
#21 12.21   this[kHandle] = new _Hash(algorithm, xofLen);
#21 12.21                   ^
#21 12.21 
#21 12.21 Error: error:0308010C:digital envelope routines::unsupported
#21 12.21     at new Hash (node:internal/crypto/hash:71:19)
#21 12.21     at Object.createHash (node:crypto:133:10)
#21 12.21     at BulkUpdateDecorator.hashFactory (/src/bocce_scorecard/node_modules/webpack/lib/util/createHash.js:145:18)
#21 12.21     at BulkUpdateDecorator.update (/src/bocce_scorecard/node_modules/webpack/lib/util/createHash.js:46:50)
#21 12.21     at RawSource.updateHash (/src/bocce_scorecard/node_modules/webpack/node_modules/webpack-sources/lib/RawSource.js:77:8)
#21 12.21     at NormalModule._initBuildHash (/src/bocce_scorecard/node_modules/webpack/lib/NormalModule.js:888:17)
#21 12.21     at handleParseResult (/src/bocce_scorecard/node_modules/webpack/lib/NormalModule.js:954:10)
#21 12.21     at /src/bocce_scorecard/node_modules/webpack/lib/NormalModule.js:1048:4
#21 12.21     at processResult (/src/bocce_scorecard/node_modules/webpack/lib/NormalModule.js:763:11)
#21 12.21     at /src/bocce_scorecard/node_modules/webpack/lib/NormalModule.js:827:5 {
#21 12.21   opensslErrorStack: [ &#39;error:03000086:digital envelope routines::initialization error&#39; ],
#21 12.21   library: &#39;digital envelope routines&#39;,
#21 12.21   reason: &#39;unsupported&#39;,
#21 12.21   code: &#39;ERR_OSSL_EVP_UNSUPPORTED&#39;
#21 12.21 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;rsquo;ll need to enable the legacy OpenSSL provider using a Node option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export NODE_OPTIONS=--openssl-legacy-provider
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yeah, building to a framework is always fun. 😏&lt;/p&gt;
&lt;p&gt;So that&amp;rsquo;s it for this weeks update.  I spent some time on Dynamo-Browse this week as well, but I haven&amp;rsquo;t actually finished that work and this log entry is long enough, so I might say more about that next week.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/03/20/getting-some-pretty.html</link>
      <pubDate>Mon, 20 Mar 2023 07:52:32 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/20/getting-some-pretty.html</guid>
      <description>&lt;p&gt;Getting some pretty strange spam emails sent to my Gmail address (which I still use). It&amp;rsquo;s the same badly formatted multi-MIME message body with different From and Subject lines. They&amp;rsquo;re trying to get… something from me? Logins, maybe? Worst phishing attempt ever!&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/a3a3150a49.png&#34; width=&#34;600&#34; height=&#34;547&#34; alt=&#34;Screenshot of a spam email with a bad multi-MIME message body asking for login details (I think)&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Updates To Dynamo-Browse And CCLM</title>
      <link>https://lmika.org/2023/03/19/updates-to-dynamobrowse.html</link>
      <pubDate>Sun, 19 Mar 2023 22:46:18 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/19/updates-to-dynamobrowse.html</guid>
      <description>&lt;p&gt;I started this week fearing that I&amp;rsquo;d have very little to write today. I actually organised some time off over the weekend where I wouldn&amp;rsquo;t be spending a lot of time on side projects. But the week started with a public holiday, which I guess acted like a bit of a time offset, so some things did get worked on.&lt;/p&gt;
&lt;p&gt;That said, most of the work done was starting or continuing things in progress, which is not super interesting at this stage. I&amp;rsquo;ll hold off on talking about those until there&amp;rsquo;s a little more there.  But there were a few things that are worth mentioning.&lt;/p&gt;
&lt;h2 id=&#34;dynamo-browse&#34;&gt;Dynamo-Browse&lt;/h2&gt;
&lt;p&gt;I found a bug in the query planner. It had to do with which index it chose when planning a query with only a single attribute. If a table has multiple GSIs that have that same attribute as the partition key (with different attributes for sort keys), the index the planner choose became effectively random. Because each index may have different records, running that query could give incomplete results.&lt;/p&gt;
&lt;p&gt;I think the query planner needs to be fixed such that any ambiguity in which index to be use would result in an error. I try to avoid putting an unnecessary need for the user to know that a particular query required a particular index. But I don&amp;rsquo;t think there&amp;rsquo;s any getting around this: the user would have to specify.&lt;/p&gt;
&lt;p&gt;But how to allow the user to specify the index to use?&lt;/p&gt;
&lt;p&gt;The fix for the script API was reasonably simple: just allow the script author to specify the index to use in the form of an option. That&amp;rsquo;s effectively what I&amp;rsquo;ve done by adding an optional &lt;code&gt;index&lt;/code&gt; field to the &lt;code&gt;session.query()&lt;/code&gt; method. When set, the specific index would be used regardless of which index the query planner would choose.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not certain how best to solve this when the user is running a query interactively. My current idea is that a menu should appear, allowing the user to select the index to use from a list. This could also include a &amp;ldquo;scan&amp;rdquo; option if no index is needed. Ideally this information will be stored alongside the query expression so that pressing &lt;kbd&gt;R&lt;/kbd&gt; would rerun the query without throwing up the prompt again.&lt;/p&gt;
&lt;p&gt;Another option is allowing the user to specify the index within the expression in some way.  Maybe in the form of a hint, as in having the user explicitly specify the sort key in a way that does&amp;rsquo;t affect the output. This is a little hacky though — sort of like those optimisations you need to do in SQL queries to nudge the planner in a particular execution plan.&lt;/p&gt;
&lt;p&gt;Another option is having the user specify the index specifically in the query. Maybe as an annotation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;color=&amp;quot;blue&amp;quot; @index(&#39;color-item-index&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or as a suffix:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;color=&amp;quot;blue&amp;quot; using index(&#39;color-item-index&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyway, this will be an ongoing thing I&amp;rsquo;m sure.&lt;/p&gt;
&lt;p&gt;One other thing I started working on in Dynamo-Browse is finally working on support for the &lt;code&gt;between&lt;/code&gt; keyword:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;age between 12 and 24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This maps directly to the &lt;code&gt;between&lt;/code&gt; statement in DynamoDB&amp;rsquo;s query expression language, so getting scan support for this was relatively easy. I do need to make the query planner know of this though, as this operation is supported in queries if it&amp;rsquo;s used with the sort key. So this is still on a branch at the moment.&lt;/p&gt;
&lt;p&gt;Finally, I&amp;rsquo;ve found myself using this tool a lot this last week and I desperately need something akin to what I&amp;rsquo;ve been calling a &amp;ldquo;fanout&amp;rdquo; command. This is a way to take the results of one query and use them in someway in  another query — almost like sub-queries in regular SQL. What I&amp;rsquo;ve been finding myself wishing I could use this for is getting the IDs of the row from a query run over the index, and just running a query for rows with those ID over the main table.  At the moment I&amp;rsquo;m left with copying the ID from the first result set, and just making a large &lt;code&gt;pk in (…)&lt;/code&gt; expression, which is far from ideal.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure whether I&amp;rsquo;d like to do this as a command, or extend the query expression in some way. Both approaches have advantages and disadvantages. That&amp;rsquo;s probably why I haven&amp;rsquo;t made any movement on this front yet.&lt;/p&gt;
&lt;h2 id=&#34;cclm&#34;&gt;CCLM&lt;/h2&gt;
&lt;p&gt;I did spend the Monday working on CCLM. I coded up a small script which took some of the ideas from &lt;a href=&#34;https://cwpat.me/misc/puzzle-level-idea-strategies/&#34;&gt;the blog post on puzzle design&lt;/a&gt; I mention last week that I could run to get some ideas. So far it&amp;rsquo;s only producing suggestions with two game elements, but it&amp;rsquo;s enough of a starting point for making puzzles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;leonmika@Stark cclm % go run ./cmd/puzzleidea
bear trap
directional walls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running it on Monday I had a go at starting work on a new level. It became clear reasonably soon after I started that I needed a new game element. So I added one, which I&amp;rsquo;ve called &amp;ldquo;kindling&amp;rdquo;.  By default it looks like a pile of wood, and is perfectively safe to walk on:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/e5c90bfb14.jpg&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;A screenshot of CCLM with a fireball about to hit kindling tiles&#34; /&gt;
&lt;p&gt;But if a fireball runs into it, it catches alight and spreads to any adjacent kindling tiles, turning them into fire tiles.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/b05fa92477.jpg&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;A screenshot of CCLM with kindling tiles catching alight and spreading to adjacent kindling tiles&#34; /&gt;
&lt;p&gt;I had an idea for this for a while. I even went to the extend of producing the graphics for this element. But needing it for this puzzle finally bought me around to finishing the work. I actually manage to make most of the changes without any changes to the Go code at all: the existing tile definition configuration was almost powerful enough to  represent this tile.&lt;/p&gt;
&lt;p&gt;One other minor thing I fixed was the alignment of the info panels on the right side of the screen. Dealing with the unaligned numbers got a bit much eventually. The cursor position, marker position, and tag numbers are properly aligned now.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/9131d0bc33.jpg&#34; width=&#34;600&#34; height=&#34;485&#34; alt=&#34;A screenshot of CCEdit with the cursor position, marker position, and tag numbers now properly aligned&#34; /&gt;
&lt;p&gt;Anyway, that&amp;rsquo;s all for this week.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/03/07/followup-from-my.html</link>
      <pubDate>Tue, 07 Mar 2023 09:13:06 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/07/followup-from-my.html</guid>
      <description>&lt;p&gt;Follow-up from my &lt;a href=&#34;https://lmika.org/2023/03/07/080720.html&#34;&gt;earlier post about ChatGTP&lt;/a&gt; this morning, it turns out I probably should&amp;rsquo;ve RTFM:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/efb26cc746.png&#34; width=&#34;600&#34; height=&#34;360&#34; alt=&#34;ChatGTP welcome screen, with the limitation &#39;Limited knowledge of world and events after 2021&#39; circled in red&#34;&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://social.lol/@andreab&#34;&gt;@andreab@social.lol&lt;/a&gt; for the tip.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/03/07/080720.html</link>
      <pubDate>Tue, 07 Mar 2023 08:07:20 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/07/080720.html</guid>
      <description>&lt;p&gt;Remember in &lt;em&gt;Gödel, Escher, Bach&lt;/em&gt; when they were saying that a (then) theoretical AI is less like a calculator and more akin to the human mind; and just like the human mind is likely to produce errors in the answers it gives? Well…&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/91ea34df7c.png&#34; width=&#34;600&#34; height=&#34;182&#34; alt=&#34;ChatGTP session where MacOS Big Sur was given as the answer to what the latest version of MacOS was&#34;&gt;
&lt;p&gt;For reference, &lt;a href=&#34;https://en.wikipedia.org/wiki/MacOS_Ventura&#34;&gt;macOS Ventura&lt;/a&gt; is the current latest release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Turn&amp;rsquo;s out the reason for this is that ChatGTP&amp;rsquo;s data-set only goes back to Nov 2021. See &lt;a href=&#34;https://lmika.org/2023/03/07/followup-from-my.html&#34;&gt;follow-up&lt;/a&gt; post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/03/04/heres-a-bit.html</link>
      <pubDate>Sat, 04 Mar 2023 16:55:32 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/03/04/heres-a-bit.html</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s a bit of a blast from the past. I managed to get &lt;em&gt;ccedit&lt;/em&gt; working again. This was the original level editor for &lt;a href=&#34;https://workingset.net/2022/12/28/i-guess-im.html,&#34;&gt;workingset.net/2022/12/2&amp;hellip;&lt;/a&gt; my &lt;a href=&#34;https://workingset.net/2022/12/28/i-guess-im.html&#34;&gt;Chips Challenge &amp;ldquo;fan game&amp;rdquo;&lt;/a&gt; I&amp;rsquo;ve been working on.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been designing a few levels for it recently, but since moving to a new Mac, the level editor I was using was going to be difficult to port. It&amp;rsquo;s QT application and the QT bindings were a pain to setup, and I rather not go through that again. I was using a Mac at the time I started working on it, but I wasn&amp;rsquo;t yet ready to go all in on MacOS. So to hedge my bets, I decided to go with QT as the UI toolkit.&lt;/p&gt;
&lt;p&gt;This was 5 years ago and I&amp;rsquo;m unlikely to go back to Linux, so choosing QT was a bit of a bad decision. I think if I had my time again, I&amp;rsquo;d go with something like AppKit.&lt;/p&gt;
&lt;p&gt;Anyway, the level editor still works but I have to log into a screen share to use it. I&amp;rsquo;d like to be able to edit levels on the machine I&amp;rsquo;m using now.&lt;/p&gt;
&lt;p&gt;The code for the original level editor was still around but it hasn&amp;rsquo;t been touched in ages. It&amp;rsquo;s basically an SDL application — the same graphics library I&amp;rsquo;m using for the actual game itself — and the &lt;a href=&#34;https://github.com/veandco/go-sdl2&#34;&gt;SDL v2 bindings&lt;/a&gt; I&amp;rsquo;m using are still maintained, so updating those were quite easy&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;One thing I did have to pull out was the Lua VM&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. The editor was using old C Lua bindings. Better Lua VMs written in pure Go are now available, so I didn&amp;rsquo;t want to keep using these old bindings anymore. In fact, I didn&amp;rsquo;t want to use Lua at all. Lua was originally used for the level scripts, but I replaced this in favour of another language (which is no longer maintained 😒, but I&amp;rsquo;m not changing it again).&lt;/p&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/aac89fc5e5.jpg&#34; alt=&#34;The original CCLM Editor&#34; title=&#34;Screenshot 2023-03-04 at 4.39.49 pm.png&#34; width=&#34;599&#34; height=&#34;486&#34; /&gt;
   &lt;figcaption&gt;The original CCLM Editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So far the editor boots up, but that&amp;rsquo;s about it. I can move the cursor around but I can&amp;rsquo;t add new tiles or load existing levels. There seems to be some weird things going on with the image name lookup. I originally thought image name were case insensitive, but after looking at the image name lookup logic in the game itself, I&amp;rsquo;m not so sure.&lt;/p&gt;
&lt;p&gt;How much time I&amp;rsquo;d like to spend on this is still a bit of a question. It all depends whether I&amp;rsquo;d like to release the game itself in some fashion. There are still questions about whether I&amp;rsquo;m allowed to, given that the graphics are not my own. Still need to think about that.&lt;/p&gt;
&lt;p&gt;But in any case, good to see the old editor again.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;The level editor was actually using older SDL v1 bindings, but it was relatively easy to port them over to v2, although some gaps are still present.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Lua was actually the second language used by the editor. The first was a Go native TCL interpretor.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/02/10/looking-at-using.html</link>
      <pubDate>Fri, 10 Feb 2023 09:16:32 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/02/10/looking-at-using.html</guid>
      <description>&lt;p&gt;Trying out Keyboard Maestro to automate some niggly things I occasionally need to do. Got my first macro working, which converts a selected string &lt;code&gt;FromCamelCase&lt;/code&gt; to &lt;code&gt;UPPER_SNAKE_CASE&lt;/code&gt;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/891ed9177c.png&#34; width=&#34;589&#34; height=&#34;598&#34; alt=&#34;Screenshot of a Keyboard Maestro macro which converts the selected string from CamelCase to UPPER_SNAKE_CASE&#34;&gt;
&lt;p&gt;So far I&amp;rsquo;m impressed. Looking forward to finding other things I can automate away with this.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2023/01/22/on-the-subject.html</link>
      <pubDate>Sun, 22 Jan 2023 13:44:04 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2023/01/22/on-the-subject.html</guid>
      <description>&lt;p&gt;On the subject of birds, I was looking at my status.lol statuses this morning. There are only a handful on there but I saw these two and it made me smile. I obviously posted them while I was looking after my sisters cockatiels last November.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/30f8b73c18.png&#34; width=&#34;600&#34; height=&#34;257&#34; alt=&#34;Two status.lol statuses made 2 months ago: one saying &#39;Can&#39;t work, Too many parrots on hand (1, which is &gt; 0)&#39; and the other saying &#39;Ok, parrots off hand. Back to work&#39;&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Poking Around The Attic Of Old Coding Projects</title>
      <link>https://lmika.org/2022/12/28/poking-around-the.html</link>
      <pubDate>Wed, 28 Dec 2022 16:27:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/12/28/poking-around-the.html</guid>
      <description>&lt;p&gt;I guess I&amp;rsquo;m in a bit of a reflective mood these pass few days because I spent the morning digging up an old project that was lying dormant for several years.  It&amp;rsquo;s effectively a clone of Chips Challenge, the old strategy game that came with the Microsoft Entertainment Pack. I was a fan of the game when I was a kid, even though I didn&amp;rsquo;t get through all the levels, and I&amp;rsquo;ve tried multiple times to make a clone of it.&lt;/p&gt;
&lt;p&gt;The earliest successful clone I can think of was back when I was using Delphi, which I think was my teens.  It&amp;rsquo;s since been lost but I do recall having a version that work and was reasonably true to the original game as possible.  It wasn&amp;rsquo;t a particularly accurate clone: I do recall some pretty significant bugs, and the code itself was pretty awful.  But it was nice to be able to do things like design my own levels (I wasn&amp;rsquo;t as internet savvy back then and I didn&amp;rsquo;t go looking for level editors for the Microsoft&amp;rsquo;s release of Chips Challenge).  Eventually I stopped working on it, and after a few updates to the family computer, plus a lack of backups or source control, there came a time where I lost it completely.&lt;/p&gt;
&lt;p&gt;Years later, I made another attempt at building a clone. I was dabbling in .Net at the time and I think I was working on it as an excuse to learn C#.  I think I got the basics of the game and associated level editor working but I didn&amp;rsquo;t get much further than that.  Either I got bored and stopped working on it.&lt;/p&gt;
&lt;p&gt;I started the latest clone nine years ago. I can&amp;rsquo;t remember the original motivation. I was just getting into Go at the time and I think it was both to learn how to build something non-trivial in the language, and to determine how good Go was for building games.  Although this is probably just a rationalisation: I&amp;rsquo;m sure the real reason was to work on something fun on the side.&lt;/p&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/465368360c.jpg&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;Screenshot of CCLM, the latest clone&#34; /&gt;
   &lt;figcaption&gt;Screenshot of CCLM, the latest clone.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Over the first five years of its life or so, I worked on it on and off, adding new game elements (tiles, sprites, etc.) and capabilities like level scripts.  One thing I am particularly proud of was building a mini-language for selecting game elements using something akin to CSS selectors.  Want to select all wall tiles?  Use the selector &lt;code&gt;SOLD&lt;/code&gt;.  How about configuring a water tile to only allow gliders and the player but only if they have the flipper?  Set the &lt;code&gt;Immune&lt;/code&gt; attribute of the water tile to &lt;code&gt;GLID,PLYR:holding(flippers)&lt;/code&gt;.  This was particularly powerful when working on tile and sprite definitions.&lt;/p&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/45b1352421.jpg&#34; width=&#34;600&#34; height=&#34;401&#34; alt=&#34;Text editor showing tile definitions with sample selectors&#34; /&gt;
   &lt;figcaption&gt;A sample of how some of the tiles are defined, including usage of the selectors.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I didn&amp;rsquo;t put as much effort into content however.  As of today, there are only 18 or so unique levels, and about half of them are ones that I consider good.  I certainly put little effort into the graphics.  Many of the tile images were just taken from the original tile-set and any additional graphics were basically inspirations from that.  This blatant copyright violation is probably why this project won&amp;rsquo;t see the light of day.&lt;/p&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/33046b877c.jpg&#34; width=&#34;600&#34; height=&#34;418&#34; alt=&#34;Screenshot of the level editor&#34; /&gt;
   &lt;figcaption&gt;The level editor, and one of the rare times when it&#39;s not crashing.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m impressed on how Go maintains its backwards capability: moving from 1.13 to 1.19 was just a matter of changing the version number in the .mod file.  I haven&amp;rsquo;t updating any of the libraries, and I&amp;rsquo;m sure the only reason why it still builds is because I haven&amp;rsquo;t dared to try.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll probably shouldn&amp;rsquo;t spend a lot of time on this.  But it was fun to revisit this for a while.&lt;/p&gt;
&lt;p&gt;One final thing: I might write more about projects I&amp;rsquo;ve long since abandoned or have worked on and haven&amp;rsquo;t released, mainly for posterity reasons but also because I like reflecting on them later.  You never know what you&amp;rsquo;d wish you documented until you&amp;rsquo;ve lost the chance to do so.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/12/27/spent-the-day.html</link>
      <pubDate>Tue, 27 Dec 2022 16:15:06 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/12/27/spent-the-day.html</guid>
      <description>&lt;p&gt;Spent the day restyling the &lt;a href=&#34;https://dynamobrowse.app&#34;&gt;Dynamo-Browse&lt;/a&gt; website. The &lt;a href=&#34;https://themes.gohugo.io/themes/hugo-theme-terminal/&#34;&gt;Terminal&lt;/a&gt; theme was fun, but over time I found the site to be difficult to navigate. And if you consider that Dynamo-Browse is not the most intuitive tool out there, an easy to navigate user manual was probably important. So I replaced that theme with &lt;a href=&#34;https://themes.gohugo.io/themes/hugo-book/&#34;&gt;Hugo-Book&lt;/a&gt;, which I think is a much cleaner layout.  After making the change, and doing a few small style fixes, I found it to be a significant improvement.&lt;/p&gt;
&lt;p&gt;I also tried my hand at designing a logo for Dynamo-Browse. The blue box that came with the Terminal theme was fine for a placeholder, but it felt like it was time for a proper logo now.&lt;/p&gt;
&lt;p&gt;I wanted something which gave the indication of a tool that worked on DynamoDB tables while also leaning into it&amp;rsquo;s TUI characteristics.  My first idea was a logo that looked like the DynamoDB icon in ASCII art. So after attempting to design something that looks like it in Affinity Designer, and passing it through an online tool which generated ASCII images from PNG, this was the result:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1d55041cde.jpg&#34; width=&#34;251&#34; height=&#34;228&#34; alt=&#34;First attempt at the Dynamo-Browse logo&#34; /&gt;
&lt;p&gt;I tried adjusting the colours of final image, and doing a few things in Acorn to thicken the ASCII characters themselves, but there was no getting around the fact that the logo just didn&amp;rsquo;t look good.  The ASCII characters were too thin and too much of the background was bleeding through.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/ff82b12ee9.jpg&#34; width=&#34;600&#34; height=&#34;158&#34; alt=&#34;Other attempts at the Dynamo-Browse logo&#34; /&gt;
&lt;p&gt;So after a break, I went back to the drawing board.  I remembered that there were actually Unicode block characters which could produce filled-in rectangles of various heights, and I wondered if using them would be a nice play on the DynamoDB logo.  Also, since the Dynamo-Browse screen consists of three panels, with only the top one having the accent colour, I thought having a similar colour banding would make a nice reference.  So I came up with this design:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1fd153bc15.jpg&#34; width=&#34;250&#34; height=&#34;250&#34; alt=&#34;Final design of the Dynamo-Browse logo&#34; /&gt;
&lt;p&gt;And I must say, I like it.  It does look a little closer to low-res pixel art than ASCII art, but what it&amp;rsquo;s trying to allude to is clear.   It looks good in both light mode and dark mode, and it also makes for a nice favicon.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s all the updates for the moment. I didn&amp;rsquo;t get around to updating the screenshots, which are in dark-mode to blend nicely with the dark Terminal theme. They actually look okay on a light background, so I can probably hold-off on this until the UI is changed in some way.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/12/18/ok-thank-you.html</link>
      <pubDate>Sun, 18 Dec 2022 13:45:29 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/12/18/ok-thank-you.html</guid>
      <description>&lt;p&gt;Ok, thank you for the insistent reminders, Patreon. I know my subscription for CGP Grey is coming up for renewal. You only need to tell me once. 🤦‍♂️&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/8c3c446504.jpg&#34; width=&#34;276&#34; height=&#34;600&#34; alt=&#34;&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Project Exploration: A Check-in App</title>
      <link>https://lmika.org/2022/12/18/project-exploration-a.html</link>
      <pubDate>Sun, 18 Dec 2022 09:22:40 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/12/17/project-exploration-a.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;m in a bit of a exploratory phase at the moment.  I&amp;rsquo;ve set aside Dynamo-Browse for now and looking to start something new.  Usually I need to start two or three things before I find something that grabs me: it&amp;rsquo;s very rare that I find myself something to work on that&amp;rsquo;s exciting before I actually start working on it.  And even if I start something, there&amp;rsquo;s a good chance that I won&amp;rsquo;t actually finish it.&lt;/p&gt;
&lt;p&gt;So it might seem weird that I&amp;rsquo;d write about these explorations at all.  Mainly I do it for prosperity reasons: just something to record so that when look back and think &amp;ldquo;what was that idea I had?&amp;rdquo;, I have something to reflect on.&lt;/p&gt;
&lt;p&gt;With that out of the way, I do have a few ideas that are worth considering.  Yesterday, I&amp;rsquo;ve started a new Flutter project in order to explore an idea for an app that&amp;rsquo;s been rattling around in my head for about a month or two.&lt;/p&gt;
&lt;p&gt;Basically the idea is this: I keep a very simple &lt;a href=&#34;https://lmika.day&#34;&gt;check-in blog&lt;/a&gt; where I post check-ins for locations that seem useful to recall down the line.  At the moment I post these check-ins manually, and usually a few hours after I leave in order to maintain some privacy of my movements.  I&amp;rsquo;ve been entertaining the idea of an app that would do this: I can post a check-in when I arrive but it won&amp;rsquo;t be published several hours after I leave.  The check-ins themselves follow a consistent format so the actual act of filling in the details can be handled by the app by presenting these properties like a regular form.  At the same time, I wanted to try something with the Micropub format and possibly the IndieAuth protocol.&lt;/p&gt;
&lt;p&gt;So I started playing around with a Flutter app that could do this.  I got to the point where I can post new check-ins and they will show up in a test blog.  Here are some screenshots:&lt;/p&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/5787bc38bf.jpg&#34; width=&#34;300&#34; height=&#34;623&#34; alt=&#34;The home screen&#34; /&gt;
   &lt;figcaption&gt;The home screen, which obviously needs a bit of work.  Pressing the floating action button will create a new checkin.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/d37fda8ccb.jpg&#34; width=&#34;300&#34; height=&#34;623&#34; alt=&#34;Selecting the check-in type&#34; /&gt;
   &lt;figcaption&gt;Selecting the type of check-in.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/6ffba44df5.jpg&#34; width=&#34;300&#34; height=&#34;623&#34; alt=&#34;Entering check-in properties&#34; /&gt;
   &lt;figcaption&gt;Each type will have different properties.  After selecting the check-in type, they will be presented to you in a form.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
   &lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/54d70c1224.jpg&#34; width=&#34;426&#34; height=&#34;185&#34; alt=&#34;How it looks on the blog&#34; /&gt;
   &lt;figcaption&gt;How it will ultimately look on the blog.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It&amp;rsquo;s still very early stages at this moment.  For one thing, check-ins are published as soon as you press &amp;ldquo;Checkin&amp;rdquo;.  Eventually they should be stored on the device and then published at a later time.  The form for entering checkins should also be slightly different based on the checkin type.  Restaurants and cafes should offer a field to enter a star rating, and airport checkins should provide a way to describe your route.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure if I will pursue this any further.  We&amp;rsquo;ll see how I feel.  But it was good to get something up and running in a couple of hours.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/11/28/flight-home-from.html</link>
      <pubDate>Tue, 29 Nov 2022 12:58:11 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/11/28/flight-home-from.html</guid>
      <description>&lt;p&gt;Flight home from the US is next week. I&amp;rsquo;m hoping for an uneventful trip. The news is not cooperating though. 😏&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/42b0d625eb.jpg&#34; width=&#34;517&#34; height=&#34;600&#34; alt=&#34;&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Dynamo-Browse Running With iSH</title>
      <link>https://lmika.org/2022/11/27/dynamobrowse-running-with.html</link>
      <pubDate>Sun, 27 Nov 2022 10:15:30 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/11/26/dynamobrowse-running-with.html</guid>
      <description>&lt;p&gt;Bit of a fun one today. After thinking about how one could go about setting up a small dev environment on the iPad, I remembered that I actually had &lt;a href=&#34;https://github.com/ish-app/ish&#34;&gt;iSH&lt;/a&gt; installed. I&amp;rsquo;ve had for a while but I&amp;rsquo;ve never really used it since I never installed tools that would be particularly useful. Thinking about what tools I could install, I was curious as to whether Dynamo-Browse could run on it.  I guess if Dynamo-Browse was a simple CLI tool that does something and produces some output, it wouldn&amp;rsquo;t be to difficult to achieve this. But I don&amp;rsquo;t think I&amp;rsquo;d be exaggerating if I said Dynamo-Browse is a bit more sophisticated than your run-of-the-mill CLI tool. Part of this is finding out not only whether building it was possible, but whether it will run well.&lt;/p&gt;
&lt;p&gt;Answering the first question involves determining which build settings to use to actually produce a binary that worked.  Dynamo-Browse is a Go app, and Go has some pretty decent &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/building-go-applications-for-different-operating-systems-and-architectures&#34;&gt;cross-compiling facilities&lt;/a&gt; so I had no doubt that such settings existed.  My first thought was a Darwin ARM binary since that&amp;rsquo;s the OS and architecture of the iPad. But one of the features of iSH is that it actually converts the binary through a &lt;a href=&#34;https://github.com/ish-app/ish#a-note-on-the-jit&#34;&gt;JIT&lt;/a&gt; before it runs it. And it turns out, after poking around a few of the included binaries using &lt;code&gt;file&lt;/code&gt;, that iSH expects binaries to be ELF 32-bit Linux binaries.&lt;/p&gt;
&lt;p&gt;Thus, setting &lt;code&gt;GOOS&lt;/code&gt; to &lt;code&gt;linux&lt;/code&gt; and &lt;code&gt;GOARCH&lt;/code&gt; to &lt;code&gt;386&lt;/code&gt; will produced a binary that would run in iSH:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;GOOS=linux GOARCH=386 go build -o dynamo-browse-linux ./cmd/dynamo-browse/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After uploading it to the iPad using Airdrop and launching in in iSH, success!&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/a8429b22b1.jpg&#34; width=&#34;600&#34; height=&#34;419&#34; alt=&#34;Dynamo-Browse running in iSH on the iPad&#34; /&gt;
&lt;p&gt;So, is runs; but does is run well? Well, sadly no. Loading and scanning the table worked okay, but doing anything in the UI was an exercise in frustration. It takes about two seconds for the key press to be recognised and to move the selected row up or down. I&amp;rsquo;m not sure what the cause of this is but I suspect it&amp;rsquo;s the screen redrawing logic. There&amp;rsquo;s a lot of string manipulation involved which is not the most memory efficient.  I&amp;rsquo;m wondering if building the app using &lt;a href=&#34;https://tinygo.org&#34;&gt;TinyGo&lt;/a&gt; would improve things.&lt;/p&gt;
&lt;p&gt;But even so, I&amp;rsquo;m reasonably impress that this worked at all. Whether it will mean that I&amp;rsquo;ll be using iSH more often for random tools I build remains to be seen, but at least the possibility is there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; While watching a re:Invent session on how a company moved from Intel to ARM, they mentioned a massive hit to performance around a function that calculates the rune length of a Unicode character. This is something that this application is constantly doing in order to layout the elements on the screen. So I&amp;rsquo;m wondering if this utility function could be the cause of the slowdown.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 2:&lt;/strong&gt; Ok, after thinking about this more, I think the last update makes no sense. For one thing, the binary iSH is running is an Intel one, and although iSH is interpreting it, I can’t imagine the slowdowns here are a result of the compiled binary. For another, both Intel and ARM (M1) builds of Dynamo-Browse work perfectly well on desktops and laptops (at least on MacOS systems). So the reason for the slowdown must be something else.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/11/10/google-photos-came.html</link>
      <pubDate>Thu, 10 Nov 2022 13:07:36 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/11/10/google-photos-came.html</guid>
      <description>&lt;p&gt;Google Photos came up with this amusing suggestion today.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/0a6cc2ab1f.png&#34; width=&#34;600&#34; height=&#34;749&#34; alt=&#34;Suggestion to rotate photos of birds&#34; /&gt;
&lt;p&gt;Just to be clear: it was the birds that were rotated, not the camera. 😀&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Audax Toolset Version 0.1.0</title>
      <link>https://lmika.org/2022/10/22/audax-toolset-version.html</link>
      <pubDate>Sat, 22 Oct 2022 11:20:36 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/10/22/audax-toolset-version.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/lmika/audax/releases/tag/v0.1.0&#34;&gt;Audax Toolset version 0.1.0&lt;/a&gt; is finally released and is available on GitHub. This version contains updates to Dynamo-Browse, which is still the only tool in the toolset so far.&lt;/p&gt;
&lt;p&gt;Here are some of the headline features.&lt;/p&gt;
&lt;h2 id=&#34;adjusting-the-displayed-columns&#34;&gt;Adjusting The Displayed Columns&lt;/h2&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/80871cfb0d.jpg&#34; width=&#34;600&#34; height=&#34;436&#34; alt=&#34;The Fields Popup&#34; /&gt;
&lt;p&gt;Consider a table full of items that look like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pk           S    00cae3cc-a9c0-4679-9e3a-032f75c2b506
sk           S    00cae3cc-a9c0-4679-9e3a-032f75c2b506
address      S    3473 Ville stad, Jersey , Mississippi 41540
city         S    Columbus
colors       M    (2 items)
  door       S    MintCream
  front      S    Tan
name         S    Creola Konopelski
officeOpened BOOL False
phone        N    9974834360
ratings      L    (3 items)
  0          N    4
  1          N    3
  2          N    4
web          S    http://www.investorgranular.net/proactive/integrate/open-source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;re interested in seeing the city, the door colour and the website in the main table which, by default, would look something like this:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/cb7da879b6.jpg&#34; width=&#34;600&#34; height=&#34;436&#34; alt=&#34;The before table layout&#34; /&gt;
&lt;p&gt;There are a few reasons why the table is laid out this way. The partition and sort key are always the first two columns, followed by any declared fields that may be used for indices.  This is followed by all the other top-level fields sorted in alphabetical order.  Nested fields are not included as columns, and maps and list fields are summarised with the number of items they hold, e.g. &lt;code&gt;(2 items)&lt;/code&gt;.  This makes it impossible to only view the columns you&amp;rsquo;re interested in.&lt;/p&gt;
&lt;p&gt;Version 0.1.0 now allows you to adjust the columns of the table.  This is done using the &lt;a href=&#34;https://audax.tools/docs/dynamo-browse/getting-around/#adjusting-the-displayed-columns&#34;&gt;Fields Popup&lt;/a&gt;, which can be opened by pressing &lt;kbd&gt;f&lt;/kbd&gt;.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/549d22fab0.jpg&#34; width=&#34;600&#34; height=&#34;436&#34; alt=&#34;Adjusting the columns in the table&#34; /&gt;
&lt;p&gt;While this popup is visible you can show columns, hide them, or move them left or right. You can also add new columns by entering a &lt;a href=&#34;https://audax.tools/docs/dynamo-browse/reference/query-expressions/&#34;&gt;Query Expression&lt;/a&gt;, which can be used to reveal the value of nested fields within the main table.  It&amp;rsquo;s now possible to change the columns of the table to be exactly what you&amp;rsquo;re interested in:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/4a02eb72c8.jpg&#34; width=&#34;600&#34; height=&#34;436&#34; alt=&#34;The after table layout&#34; /&gt;
&lt;h2 id=&#34;read-only-mode-and-result-limits&#34;&gt;Read-only Mode And Result Limits&lt;/h2&gt;
&lt;p&gt;Version 0.1.0 also contains some niceties for reducing the impact of working on production tables.  Dynamo-Browse can now be started in read-only mode using the &lt;code&gt;-ro&lt;/code&gt; flag, which will disable all write operations — a useful feature if you&amp;rsquo;re paranoid about accidentally modifying data on production databases.&lt;/p&gt;
&lt;p&gt;Another new flag is &lt;code&gt;-default-limit&lt;/code&gt; which will change the default number of items returned from scans and queries from 1000 to whatever you want.  This is useful to cut down on the number of read capacity units Dynamo-Browse will use on the initial scans of production tables.&lt;/p&gt;
&lt;p&gt;These settings are also changeable from while Dynamo-Browse using the new &lt;a href=&#34;https://audax.tools/docs/dynamo-browse/reference/commands/#set&#34;&gt;set command&lt;/a&gt;:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1afbded2a3.jpg&#34; width=&#34;600&#34; height=&#34;436&#34; alt=&#34;Using the set command in Dynamo-Browse&#34; /&gt;
&lt;h2 id=&#34;progress-indicators-and-cancelation&#34;&gt;Progress Indicators And Cancelation&lt;/h2&gt;
&lt;p&gt;Dynamo-Browse now indicates running operations, like scans or queries, with a spinner.  This improves the user experience of prior versions of Dynamo-Browse, which gave no feedback of running operations whatsoever and would simply &amp;ldquo;pop-up&amp;rdquo; the result of such operations in a rather jarring way.&lt;/p&gt;
&lt;p&gt;With this spinner visible in the status bar, it is also now possible to cancel an operation by pressing &lt;kbd&gt;Ctrl-C&lt;/kbd&gt;.  You have the option to view any partial results that were already retrieved at the time.&lt;/p&gt;
&lt;h2 id=&#34;other-changes&#34;&gt;Other Changes&lt;/h2&gt;
&lt;p&gt;Here are some of the other bug-fix and improvements that are also included in this release:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Audax toolset is now distributed via Homebrew.  Check out the &lt;a href=&#34;https://audax.tools/download/&#34;&gt;Downloads&lt;/a&gt; page for instructions.&lt;/li&gt;
&lt;li&gt;A new &lt;a href=&#34;https://audax.tools/docs/dynamo-browse/reference/commands/#mark&#34;&gt;mark command&lt;/a&gt; to mark all, unmark all, or toggle marked rows.  The &lt;code&gt;unmark&lt;/code&gt; command is now an alias to &lt;code&gt;mark none&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Query expressions involving the partition and sort key of the main table are now executed as a DynamoDB queries, instead of scans.&lt;/li&gt;
&lt;li&gt;The query expression language now supports conjunction, disjunction, and dot references.&lt;/li&gt;
&lt;li&gt;Fixed a bug which was not properly detecting whether MacOS was in light mode.  This was making some highlighted colours hard to see while in dark mode.&lt;/li&gt;
&lt;li&gt;Fixed the table-selection filter, which was never properly working since the initial release.&lt;/li&gt;
&lt;li&gt;Fixed the back-stack service to prevent duplicate views from being pushed.&lt;/li&gt;
&lt;li&gt;Fixed some conditions which were causing seg. faults.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full details of the changes can be &lt;a href=&#34;https://github.com/lmika/audax/releases/tag/v0.1.0&#34;&gt;found on GitHub&lt;/a&gt;.  Details about the various features can also be found in the &lt;a href=&#34;https://audax.tools/docs/dynamo-browse/&#34;&gt;user manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, although it does not touch on any of the features described above, I recorded a introduction video on the basics of using Dynamo-Browse to view items of a DynamoDB table:&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/cQnTIg1_tfg&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;No promises, but I may record further videos touching on other aspects of the tool in the future.  If that happens, I&amp;rsquo;ll make sure to mention them here.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Or you can like, comment or subscribe on YouTube if that&amp;rsquo;s your thing 😛.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/10/09/working-on-recording.html</link>
      <pubDate>Sun, 09 Oct 2022 10:27:29 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/10/09/working-on-recording.html</guid>
      <description>&lt;p&gt;Working on recording &lt;a href=&#34;https://lmika.org/2022/09/23/tried-something-different.html&#34;&gt;that how-to video&lt;/a&gt; again this morning.  Managed to get a take that I&amp;rsquo;m happy with.  This is after maybe 35 recordings in total which didn&amp;rsquo;t work (wow, video is not easy).  Now downloading the trial version of Final Cut Pro to try and edit the thing.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/4c2badd194.png&#34; width=&#34;600&#34; height=&#34;354&#34; alt=&#34;Screenshot of 25 recorded takes.&#34;&gt;
</description>
    </item>
    
    <item>
      <title>Overlay Composition Using Bubble Tea</title>
      <link>https://lmika.org/2022/09/24/overlay-composition-using.html</link>
      <pubDate>Sat, 24 Sep 2022 14:58:24 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/09/24/overlay-composition-using.html</guid>
      <description>&lt;p&gt;Working on a new feature for Dynamo-Browse which will allow the user to modify the columns of the table: move them around, sort them, hide them, etc.  I want the feature to be interactive instead of a whole lot of command incantations that are tedious to write.  I also kind of want the table whose columns are being manipulated to be visible, just so that the affects of the change would be apparent to the user while they make them.&lt;/p&gt;
&lt;p&gt;This is also an excuse to try something out using Bubble Tea — the TUI framework I&amp;rsquo;m using — which is to add the ability to display overlays.  These are UI elements (the overlay) that appear above other UI elements (the background).  The two are composed into a single view such that it looks like the overlay is obscuring parts of the background, similar to how windows work in MacOS.&lt;/p&gt;
&lt;p&gt;Bubble Tea doesn&amp;rsquo;t have anything like this built in, and the way views are rendered in Bubble Tea doesn&amp;rsquo;t make this super easy.  The best way to describe how views work is to think of them as &amp;ldquo;scanlines.&amp;rdquo;  Each view produces a string with ANSI escape sequences to adjust the style.  The string can contain newlines which can be used to move the cursor down while rendering.  Thus, there&amp;rsquo;s no easy way position the cursor at an arbitrary position and render characters over the screen.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So, I thought I&amp;rsquo;d tackle this myself.&lt;/p&gt;
&lt;h2 id=&#34;attempt-1&#34;&gt;Attempt 1&lt;/h2&gt;
&lt;p&gt;On the surface, the logic for this is simple.  I&amp;rsquo;ll render the background layer up to the top most point of the overlay.  Then for each scan line within the top and bottom border of the overlay, I&amp;rsquo;ll render the background up to the overlay&amp;rsquo;s left border, then the contents of the overlay itself, then the rest of the background from the right border of the overlay.&lt;/p&gt;
&lt;p&gt;My first attempt was something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;line := backgroundScanner.Text()
if row &amp;gt;= c.overlayY &amp;amp;&amp;amp; row &amp;lt; c.overlayY+c.overlayH {
    // When scan line is within top &amp;amp; bottom of overlay
    compositeOutput.WriteString(line[:c.foreX])
    
    foregroundScanPos := row - c.overlayY
    if foregroundScanPos &amp;lt; len(foregroundViewLines) {
        displayLine := foregroundViewLines[foregroundScanPos]
        compositeOutput.WriteString(lipgloss.PlaceHorizontal(
            c.overlayW,
            lipgloss.Left,
            displayLine,
            lipgloss.WithWhitespaceChars(&amp;quot; &amp;quot;)),
        )
    }
    
    compositeOutput.WriteString(line[c.overlayX+c.overlayW:])
} else {
    // Scan line is beyond overlay boundaries of overlay
    compositeOutput.WriteString(line)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&amp;rsquo;s how that looked:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/670f516c6d.jpg&#34; width=&#34;600&#34; height=&#34;428&#34; alt=&#34;Attempt 1&#34; /&gt;
&lt;p&gt;Yeah, not great.&lt;/p&gt;
&lt;p&gt;Turns out I forgot two fundamental things.  One is that the indices of Go strings works on the underlying byte array, not runes.  This means that attempting to slice a string between multi-byte Unicode runes would produce junk.  It&amp;rsquo;s a little difficult to find this in the Go Language Guide apart from this cryptic line:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A string&amp;rsquo;s bytes can be accessed by integer indices 0 through len(s)-1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But it&amp;rsquo;s relatively easy to test within the Go playground:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import &amp;quot;fmt&amp;quot;

func main() {
    fmt.Println(&amp;quot;世界😀😚🙃😇🥸😎&amp;quot;[1:6])                   // ��界
    fmt.Println(string([]rune(&amp;quot;世界😀😚🙃😇🥸😎&amp;quot;)[1:6]))   // 界😀😚🙃😇
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second issue is that I&amp;rsquo;m splitting half-way through an ANSI escape sequence.  I don&amp;rsquo;t know how long the escape sequence is to style the header of the item view, but I&amp;rsquo;m betting that it&amp;rsquo;s longer than 5 bytes (the overlay is to be position at column 5).  That would explain why there&amp;rsquo;s nothing showing up to the left of the overlay for most rows, and why the sequence &lt;code&gt;6;48;5;188m&lt;/code&gt; is there.&lt;/p&gt;
&lt;h2 id=&#34;attempt-2&#34;&gt;Attempt 2&lt;/h2&gt;
&lt;p&gt;I need to modify the logic so that zero-length escape sequences are preserved.  Fortunately, one of Bubble Tea&amp;rsquo;s dependency is &lt;a href=&#34;github.com/muesli/reflow&#34;&gt;reflow&lt;/a&gt;, which offers a bunch of nice utilities for dealing with ANSI escape sequences.  The function that looks promising is &lt;code&gt;truncate.String&lt;/code&gt;, which will truncate a string at a given width.&lt;/p&gt;
&lt;p&gt;So changing the logic slightly, the solution became this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// When scan line is within top &amp;amp; bottom of overlay
compositeOutput.WriteString(truncate.String(line, uint(c.overlayX)))

foregroundScanPos := r - c.overlayY
if foregroundScanPos &amp;lt; len(foregroundViewLines) {
    displayLine := foregroundViewLines[foregroundScanPos]
    compositeOutput.WriteString(lipgloss.PlaceHorizontal(
        c.overlayW,
        lipgloss.Left,
        displayLine,
        lipgloss.WithWhitespaceChars(&amp;quot; &amp;quot;),
    ))
}

rightStr := truncate.String(line, uint(c.foreX+c.overlayW))
compositeOutput.WriteString(line[len(rightStr):])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results are a little better.  At least the left side of the overlay looked OK now:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/0a01d51906.jpg&#34; width=&#34;600&#34; height=&#34;428&#34; alt=&#34;Attempt 2&#34; /&gt;
&lt;p&gt;But there are still problems here.  Notice the &lt;code&gt;[0m&lt;/code&gt; at the right side of the overlay on the selected row.  I can assure you that&amp;rsquo;s not part of the partition key; take a look at the item view to see for yourself.  And while you&amp;rsquo;re there, notice the header of the item view?  That should be a solid grey bar, but instead it&amp;rsquo;s cut off at the overlay.&lt;/p&gt;
&lt;p&gt;I suspect that &lt;code&gt;rightStr&lt;/code&gt; does not have the proper ANSI escape sequences.  I&amp;rsquo;ll admit that the calculation used to set &lt;code&gt;rightStr&lt;/code&gt; is a bit of a hack.  I&amp;rsquo;ll need to replace it with a proper way to detect the boundary of an ANSI escape sequence.  But it&amp;rsquo;s more than just that.  If an ANSI escape sequence starts off at the left side of the string, and continues on &amp;ldquo;underneath&amp;rdquo; the overlay, it should be preserved on the right side of the overlay as well.  The styling of the selected row and the item view headers are examples of that.&lt;/p&gt;
&lt;h2 id=&#34;attempt-3&#34;&gt;Attempt 3&lt;/h2&gt;
&lt;p&gt;So here&amp;rsquo;s what I&amp;rsquo;m considering: we &amp;ldquo;render&amp;rdquo; the background underneath the overlay to a null buffer while recording any escape sequences that were previously set on the left, or were changed underneath the overlay.  We also keep track of the number of visible characters that were seen.  Once the scan line position reached the right border of the overlay, we replay all the ANSI escape sequences in the order that were found, and then render the right hand side of the scan line from the first visible character.&lt;/p&gt;
&lt;p&gt;I was originally considering rendering these characters to a null reader, but what I actually did was simply count the length of visible characters in a loop.  The function to do this looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func (c *Compositor) renderBackgroundUpTo(line string, x int) string {
    ansiSequences := new(strings.Builder)
    posX := 0
    inAnsi := false

    for i, c := range line {
        if c == ansi.Marker {
            ansiSequences.WriteRune(c)
            inAnsi = true
        } else if inAnsi {
            ansiSequences.WriteRune(c)
            if ansi.IsTerminator(c) {
                inAnsi = false
            }
        } else {
            if posX &amp;gt;= x {
                return ansiSequences.String() + line[i:]
            }
            posX += runewidth.RuneWidth(c)
        }
    }
    return &amp;quot;&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the value set to &lt;code&gt;rightStr&lt;/code&gt; is changed to simply used this function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rightStr := c.renderBackgroundUpTo(line, c.foreX+c.overlayW)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the result:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/9a0dabfcb0.jpg&#34; width=&#34;600&#34; height=&#34;428&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;That looks a lot better.  Gone are the artefacts from splitting in ANSI escape sequences, and the styling of the selected row and item view header are back.&lt;/p&gt;
&lt;p&gt;I can probably work with this.  I&amp;rsquo;m hoping to use this to provide a way to customise the columns with the table view.  It&amp;rsquo;s most likely going to power other UI elements as well.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Well, there might be a way using ANSI escape sequences, but that goes against the approach of the framework.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/09/20/090542.html</link>
      <pubDate>Tue, 20 Sep 2022 10:05:42 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/09/20/090542.html</guid>
      <description>&lt;p&gt;🎉&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/3456d11b9f.png&#34; width=&#34;600&#34; height=&#34;201&#34; alt=&#34;Notification of PagerDuty email stating that I am off call&#34; /&gt;
</description>
    </item>
    
    <item>
      <title>Intermediary Representation In Dynamo-Browse Expressions</title>
      <link>https://lmika.org/2022/09/16/intermediary-representation-in.html</link>
      <pubDate>Fri, 16 Sep 2022 23:37:20 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/09/16/intermediary-representation-in.html</guid>
      <description>&lt;p&gt;One other thing I did in Dynamo-Browse is change how the query AST produced the actual DynamoDB call.&lt;/p&gt;
&lt;p&gt;Previously, the AST produced the DynamoDB call directly.  For example, if we were to use the expression &lt;code&gt;pk = &amp;quot;something&amp;quot; and sk ^= &amp;quot;prefix&amp;quot;&lt;/code&gt;, the generated AST may look something like the following:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/f5b8e16f36.jpg&#34; width=&#34;600&#34; height=&#34;387&#34; alt=&#34;AST from the parse expression&#34; /&gt;
&lt;p&gt;The AST will then be traversed to determine whether this could be handled by either running a query or a scan.  This is called &amp;ldquo;planning&amp;rdquo; and the results of this will determine which DynamoDB API endpoint will be called to produce the result.  This expression may produce a call to DynamoDB that would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;client.Query(&amp;amp;dynamodb.QueryInput{
    TableName: &amp;quot;my-table-name&amp;quot;,
    KeyConditionExpression: &amp;quot;#0 = :0 and beings_with(#1, :1)&amp;quot;,
    ExpressionAttributeNames: map[string]string{
        &amp;quot;#0&amp;quot;: &amp;quot;pk&amp;quot;,
        &amp;quot;#1&amp;quot;: &amp;quot;sk&amp;quot;,
    },
    ExpressionAttributeValues: map[string]types.AttributeValue{
         &amp;quot;:0&amp;quot;: &amp;amp;types.StringAttributeValue{ Value: &amp;quot;something&amp;quot; },
         &amp;quot;:1&amp;quot;: &amp;amp;types.StringAttributeValue{ Value: &amp;quot;prefix&amp;quot; },
    },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, instead of determining the various calls to DynamoDB itself, the AST will generate an intermediary representation, something similar to the following:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/0839b39179.jpg&#34; width=&#34;600&#34; height=&#34;514&#34; alt=&#34;Intermediary representation tree&#34; /&gt;
&lt;p&gt;The planning traversal will now happen off this tree, much like it did over the AST.&lt;/p&gt;
&lt;p&gt;For such a simple expression, the benefits of this extra step may not be so obvious.  But there are some major advantages that I can see from doing this.&lt;/p&gt;
&lt;p&gt;First, it simplifies the planning logic quite substantially.  If you compare the first tree with the second, notice how the nodes below the &amp;ldquo;and&amp;rdquo; node are both of type &amp;ldquo;binOp&amp;rdquo;.  This type of node represents a binary operation, which can either be &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;^=&lt;/code&gt;, plus all the other binary operators that may come along.  Because so many operators are represented by this single node type, the logic of determining whether this part of the expression can be represented as a query will need to look something like the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First determine whether the operation is either &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;^=&lt;/code&gt; (or whatever else)&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s &lt;code&gt;=&lt;/code&gt; and the field on the left is either a partition or sort key, this can be represented as a query&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s &lt;code&gt;^=&lt;/code&gt;, first determine whether the operand is a string, (if not, fail) and then determine whether the field on the left is a sort key.  If so, then this can be query.&lt;/li&gt;
&lt;li&gt;Otherwise, it will have to be a scan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is mixing various stages of the compilation phase in a single traversal: determining what the operator is, determining whether the operands are valid (&lt;code&gt;^=&lt;/code&gt; must have a string operand), and working out how we can run this as a query, if at all.  You can imagine the code to do this being large and fiddly.&lt;/p&gt;
&lt;p&gt;With the IR tree, the logic can be much simpler.  The work surrounding the operand is done when the AST tree is traverse.  This is trivial: if it&amp;rsquo;s &lt;code&gt;=&lt;/code&gt; then produce a &amp;ldquo;fieldEq&amp;rdquo;; if it&amp;rsquo;s &lt;code&gt;^=&lt;/code&gt; then produce a &amp;ldquo;fieldBeginsWith&amp;rdquo;, and so on.  Once we have the IR tree, we know that when we encounter a &amp;ldquo;fieldEq&amp;rdquo; node, this attribute can be represented as a query if the field name is either the partition or sort key.  And when we encounter the &amp;ldquo;fieldBeginsWith&amp;rdquo; node, we know we can use a query if the field name is the sort key.&lt;/p&gt;
&lt;p&gt;Second, it allows the AST to be richer and not tied to how the actual call is planned out.  You won&amp;rsquo;t find the &lt;code&gt;^=&lt;/code&gt; operator in any of the expressions DynamoDB supports: this was added to Dynamo-Browse&amp;rsquo;s expression language to make it easier to write.  But if we were to add the &amp;ldquo;official&amp;rdquo; function for this as well — &lt;code&gt;begins_with()&lt;/code&gt; — and we weren&amp;rsquo;t using the IR, we would need to have the planning logic for this in two places.  With an IR, we can simply have both operations produce a &amp;ldquo;fieldBeginsWith&amp;rdquo; node.  Yes, there could be more code encapsulated by this IR node (there&amp;rsquo;s actually less) but it&amp;rsquo;s being leverage by two separate parts of the AST.&lt;/p&gt;
&lt;p&gt;And since expressions are not directly mappable to DynamoDB expression types, we can theoretically do things like add arithmetic operations or a nice suite of utility functions.  Provided that these produce a single result, these parts of the expression can be evaluated while the IR is being built, and the literal value returned that can be used directly.&lt;/p&gt;
&lt;p&gt;It felt like a few other things went right with this decision.  I was expecting this to take a few days, but  I was actually able to get it built in a single evening.  I&amp;rsquo;m also happy about how maintainable the code turned out to be.  Although there are two separate tree-like types that need to be managed, both have logic which is much simpler than what we were dealing with before.&lt;/p&gt;
&lt;p&gt;All in all, I&amp;rsquo;m quite happy with this decision.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/09/13/saw-someone-at.html</link>
      <pubDate>Tue, 13 Sep 2022 08:50:07 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/09/13/saw-someone-at.html</guid>
      <description>&lt;p&gt;Saw someone at work use &lt;a href=&#34;https://numi.app&#34;&gt;Numi&lt;/a&gt; to show some maths so I&amp;rsquo;m giving it a try. Only just started using it but I already like it. Feels very similar to Tot and Boop: a small MacOS utility that fits nicely in that middle-ground between Calculator and a spreadsheet.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/3177baf984.png&#34; width=&#34;600&#34; height=&#34;413&#34; alt=&#34;Screenshot of Numi&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/09/03/it-could-be.html</link>
      <pubDate>Sat, 03 Sep 2022 12:24:47 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/09/03/it-could-be.html</guid>
      <description>&lt;p&gt;It could be that I&amp;rsquo;m just an old fogie that doesn&amp;rsquo;t like whimsy, but chalk me up as someone who doesn&amp;rsquo;t like the wiggling seek bar in Android&amp;rsquo;s playback notification.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/482383aafd.jpg&#34; width=&#34;600&#34; height=&#34;600&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/08/31/you-can-tell.html</link>
      <pubDate>Wed, 31 Aug 2022 12:15:29 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/08/31/you-can-tell.html</guid>
      <description>&lt;p&gt;You can tell I&amp;rsquo;m bored at work when I spend time building stupid little utilities for myself instead of actually doing the task I should be doing. Today&amp;rsquo;s stupid little utility: a TUI tool to list merge requests I&amp;rsquo;ve posted for review. Saves a trip to the browser.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/976bcb408c.png&#34; width=&#34;600&#34; height=&#34;423&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/08/26/those-accept-yours.html</link>
      <pubDate>Fri, 26 Aug 2022 13:55:23 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/08/26/those-accept-yours.html</guid>
      <description>&lt;p&gt;Those &amp;ldquo;Accept Yours&amp;rdquo; &amp;amp; &amp;ldquo;Accept Theirs&amp;rdquo; buttons in GoLand&amp;rsquo;s conflicts dialog are a bit of a tease. Might be that I&amp;rsquo;m missing something (which is possible) but I though a file appearing here means that I manually need to review it. Would anyone blindly accept changes like this?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/8a5a9a686b.png&#34; width=&#34;330&#34; height=&#34;153&#34; alt=&#34;Accept Yours, Accept Theirs &amp; Merge buttons in Conflict resolution dialog&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/07/25/ive-been-seeing.html</link>
      <pubDate>Mon, 25 Jul 2022 08:06:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/07/25/ive-been-seeing.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been seeing this message an awful lot recently when I&amp;rsquo;m trying to use mobile data.  I originally thought it was Telstra, but now I suspect it&amp;rsquo;s Android, as restarting the phone seemed to have fixed it.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/78de8ec2f7.jpg&#34; width=&#34;276&#34; height=&#34;600&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/06/07/the-names-for.html</link>
      <pubDate>Tue, 07 Jun 2022 22:30:39 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/06/07/the-names-for.html</guid>
      <description>&lt;p&gt;The names for GitHub Codespaces are randomly generated, but I kinda like the one chosen for this website repo.  Seems fitting in a way.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/6ca60aeede.png&#34; width=&#34;394&#34; height=&#34;297&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title>WWDC Videos In Broadtail</title>
      <link>https://lmika.org/2022/05/16/wwdc-videos-in.html</link>
      <pubDate>Mon, 16 May 2022 21:20:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/05/16/wwdc-videos-in.html</guid>
      <description>&lt;p&gt;Some more work on Broadtail.  This time, I added the ability to use it to download Apple WWDC videos.&lt;/p&gt;
&lt;p&gt;The way it works is based on the existing RSS feed concept.  In order to get the list of videos for a particular WWDC year, you “subscribe” to that by setting up a feed with the new “Apple Developer Videos” type.  The external ID is taken from the URL slug of the web-site that Apple publishes the session videos.  For example, for &lt;a href=&#34;https://developer.apple.com/videos/wwdc2021&#34;&gt;WWDC 2021&lt;/a&gt;, the external ID would be “wwdc2021”.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/d2c645d1f4.jpg&#34; width=&#34;600&#34; height=&#34;451&#34; alt=&#34;&#34; /&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/9cafe29f43.jpg&#34; width=&#34;600&#34; height=&#34;451&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;Downloading the videos is more or less the same.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/805276c746.jpg&#34; width=&#34;600&#34; height=&#34;451&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;There are a few differences between this feed type, and the YouTube RSS feed.  For instance, it only makes use of what is available from the website, which means details like publishing date or duration are not really available.  This is why the “Publishing” date is displayed as “unknown”.  That’s also why the videos are arranged in alphabetical order and the feed itself is not automatically refreshed (although doing so manually by clicking “Refresh” within the feed page will work).  These are actually properties that can now be applied to all feeds of one wishes, although the YouTube feeds are still arranged in reverse chronological order by default.&lt;/p&gt;
&lt;p&gt;From a coding perspective, this involved a lot of refactoring.  I was hoping to move to a more generic feed and video type, but this was the feature that eventually got me to do so.  Thing is that if I wanted to add more feed and video types in the future, it should be easier to do so.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Feed Rules In Broadtail</title>
      <link>https://lmika.org/2022/05/09/feed-rules-in.html</link>
      <pubDate>Mon, 09 May 2022 21:17:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/05/09/feed-rules-in.html</guid>
      <description>&lt;p&gt;Generally, when there’s a video that I’m interesting in watching, I take a look at Broadtail to see if it’s available.  When it is, I go ahead and download it.&lt;/p&gt;
&lt;p&gt;However, some videos take a long time to download — we’re talking 10 hours or so — and they’re usually published when I’m not looking, like during the night when I’m asleep (thank’s time-zones).  So I’d thought it would be nice for Broadtail to kick off the download for me when the video shows up in the feed.&lt;/p&gt;
&lt;p&gt;So I’ve added Feed Rules to do this.&lt;/p&gt;
&lt;p&gt;Feed rules are very simple automations that happen when new items are found in during the RSS feed poll.  When the video shows up in the feed, and matches the rule condition, Broadtail will perform the rule action for that video.&lt;/p&gt;
&lt;p&gt;Feed Rules are added as a new sub-section in “Settings”, which itself is a new top-level section of the app (the “General” sub-section is empty at this stage).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1ce2e30fc0.jpg&#34; width=&#34;600&#34; height=&#34;462&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;Feed Rules consist of a name, whether the rule is active, a set of conditions, and a set of action.  A feed item will need to match all the conditions of the rule in order for the actions to be performed.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/24081e093b.jpg&#34; width=&#34;600&#34; height=&#34;463&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;The conditions of a feed rule touch upon the following properties of a feed item:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The feed in which it appears in. This can be set to “any” to apply the rule to all feed items.&lt;/li&gt;
&lt;li&gt;Whether the title matches a given string. The match rules are similar to the searches in the feed item list views, which are appearance of each of the space separated tokens somewhere in the title (in any case) with phrases appearing as quoted strings.&lt;/li&gt;
&lt;li&gt;Whether the description matches a given string.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a feed item matches all the conditions, Broadtail can perform the following actions for the feed item:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start a download of the video&lt;/li&gt;
&lt;li&gt;Mark the feed item as a favourite&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There might be more conditions or actions added in the future.  So far this seems to be the bare minimum to make the feature usable.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/04/23/oh-it-better.html</link>
      <pubDate>Sat, 23 Apr 2022 10:25:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/04/23/oh-it-better.html</guid>
      <description>&lt;p&gt;Oh, I hope not.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/248a764fda.png&#34; width=&#34;600&#34; height=&#34;269&#34; alt=&#34;An exceeding long download estimation&#34; /&gt;
</description>
    </item>
    
    <item>
      <title>New AWS Tools Commands</title>
      <link>https://lmika.org/2022/03/25/new-aws-tools.html</link>
      <pubDate>Fri, 25 Mar 2022 21:05:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/03/25/new-aws-tools.html</guid>
      <description>&lt;p&gt;For a while now, I’ve been wanting some tools which would help manage AWS resources that would also run in the terminal.  I know in most circumstances the AWS console would work, but I know for myself, there’s a lot of benefit from doing this sort of administration from the command line.&lt;/p&gt;
&lt;p&gt;I use the terminal a lot when I’m developing or investigating something.  Much of the time while I’m in the weeds I’ve got a bunch of tabs with tools running and producing output, and I’m switching between them as I try to get something working across a bunch of systems.&lt;/p&gt;
&lt;p&gt;This is in addition to cases when I need to manage an AWS mock running on the local machine.  The AWS console will not work then.&lt;/p&gt;
&lt;p&gt;At the start of the week, I was thinking of at least the following three tools that I would like to see exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A TUI browser/workspace for DynamoDB tables&lt;/li&gt;
&lt;li&gt;A TUI workspace for monitoring SQS queues&lt;/li&gt;
&lt;li&gt;Some tool for reading JSON style log files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As of yesterday, I actually got around to building the first two.&lt;/p&gt;
&lt;p&gt;The first is a tool for browsing DynamoDB tables, which I’m calling &lt;code&gt;dynamo-browse&lt;/code&gt; (yes, the names are not great).  This tool does a scan of a DynamoDB table, and shows the resulting items in a table.  Each item can be inspected in full in the lower half of the window by moving the selection.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/91ca4f329a.jpg&#34; width=&#34;600&#34; height=&#34;430&#34; alt=&#34;Dynamo-Browse&#34; /&gt;
&lt;p&gt;At the moment this tool only does a simple scan, and some very lightweight editing of items (duplicate and delete).  But already it’s proven useful with the task I was working on, especially when I came to viewing the contents of test DynamoDB instances running on the local machine.&lt;/p&gt;
&lt;p&gt;The second tool is used for monitoring SQS queues.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/f13d3900a2.jpg&#34; width=&#34;600&#34; height=&#34;430&#34; alt=&#34;SQS-Browse&#34; /&gt;
&lt;p&gt;This will poll for SQS messages from a queue and display them in the table here.  The message itself will be pretty printed in the lower half of the screen.  Messages can also be pushed to another queue.  That’s pretty much it so far.&lt;/p&gt;
&lt;p&gt;There are a bunch of things I’d like to do in these tools.  Here’s a list of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Queries in DynamoDB:&lt;/strong&gt; There’s currently no way to run queries or filter the resulting items. I’m hoping to design a small query language to do this. I’m already using Participal to power a very simple expression language to duplicate items, so as long as I can design something that is expressive enough and knows how to use particular indices, I think this should work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Putting brand new items in DynamoDB:&lt;/strong&gt; At the moment you can create new items based on existing items in a way, but there’s currently no way to create brand new items or adjust the attributes of existing items. For this, I’d like to see an “edit item” mode, where you can select the attribute and edit the value, change the type, add or remove attributes, etc. This would require some UI rework, which is already a bit tentative at this stage (it was sort of rushed together, and although some architectural changes have been made to the M and the C in MVC, work on the V is still outstanding).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preview item changes before putting them to DynamoDB:&lt;/strong&gt; This sort of extends the point above, where you see the diff between the old item and new item before it’s put into DynamoDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workspaces in SQS Browse:&lt;/strong&gt; One idea I have for SQS browse is the notion of “workspace”. This is a persistent storage area located on the disk where all the messages from the queue would be saved to. Because SQS browse is currently pulling messages from the SQS queue, I don’t want the user to get into a state where they’ve lost their messages for good. The idea is that a workspace is always created when SQS browse is launch. The user can choose the workspace file explicitly, but if they don’t, then the workspace will be created in the temp directory. Also implicit in this is support for opening existing workspaces to continue work in them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple queues in SQS Browse:&lt;/strong&gt; Something that I’d like to see in SQS Browse is the ability to deal with multiple queues. Say you’re pulling from one queue and you’d like to push it to another. You can use a command to add a queue to the workspace. Then, you can do things like poll the queue, push messages in the workspace to the queue, monitor it’s queue length, etc. Queues would be addressable by number or some other way, so you can simply run the command push 2 to push the current message to queue 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As for when I’ll actively work on these tools.  It will probably be when I need to use them.  But in the short term, I’m glad I got the opportunity to start working on them.  They’ve already proven quite useful to me.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2022/03/11/this-is-by.html</link>
      <pubDate>Fri, 11 Mar 2022 08:19:34 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/03/11/this-is-by.html</guid>
      <description>&lt;p&gt;This is by far the most useful quick action I&amp;rsquo;ve made in Automator.  It generates a UUID, and places it in the pasteboard.  I&amp;rsquo;ve got it bound to &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Opt&lt;/kbd&gt;+&lt;kbd&gt;Cmd&lt;/kbd&gt;+&lt;kbd&gt;U&lt;/kbd&gt; and I&amp;rsquo;ve been using it constantly over the last week (writing a lot of tests with test data).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2022/a3248e912a.png&#34; width=&#34;600&#34; height=&#34;493&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title>Some More Updates of Broadtail</title>
      <link>https://lmika.org/2022/03/05/some-more-updates.html</link>
      <pubDate>Sat, 05 Mar 2022 20:49:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2022/03/05/some-more-updates.html</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve made some more changes to Broadtail over the last couple of weeks.&lt;/p&gt;
&lt;p&gt;The home page now shows a list of recently published videos below the currently running jobs.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/a191cab735.jpg&#34; width=&#34;600&#34; height=&#34;460&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;Clicking through to “Show All” displays all the published videos.  A simple filter can be applied to filter them down to videos with titles containing the keywords (note: nothing fancy with the filter, just tokenisation and an OR query).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/21c680453b.jpg&#34; width=&#34;600&#34; height=&#34;460&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;Finally, items can now be favourited.  This can be used to select videos that you may want to download in the future.  I personally use this to keep the list of “new videos” in the Plex server these videos go to to a minimum.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/6fa1a51a80.jpg&#34; width=&#34;600&#34; height=&#34;453&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title>Feeds In Broadtail</title>
      <link>https://lmika.org/2021/11/06/feeds-in-broadtail.html</link>
      <pubDate>Sat, 06 Nov 2021 21:32:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/11/06/feeds-in-broadtail.html</guid>
      <description>&lt;p&gt;My quest to watch YouTube without using YouTube got a little closer recently with the addition of feeds in Broadtail.  This uses the &lt;a href=&#34;https://mjtsai.com/blog/2020/01/16/youtube-rss-feeds/&#34;&gt;YouTube RSS feed endpoint&lt;/a&gt; to list videos recently added to a channel or playlist.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1e7e3beeb5.jpg&#34; width=&#34;600&#34; height=&#34;409&#34; alt=&#34;Feed listing, in all it&#39;s 90&#39;s web style glory.&#34; /&gt;
&lt;p&gt;There are a bunch of channels that I watch regularly but I&amp;rsquo;m very hesitant to subscribe to them within YouTube itself (sorry YouTubers, but I choose not to smash that bell icon).  I&amp;rsquo;m generally quite hesitant to give any signal to YouTube about my watching habits, feeding their machine learning models even more information about myself.  But I do want to know when new videos are available, so that I can get them into Plex once they&amp;rsquo;re released.  There is where feeds come in handy.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1bfefa3950.jpg&#34; width=&#34;600&#34; height=&#34;496&#34; alt=&#34;Recent videos of a feed.&#34; /&gt;
&lt;p&gt;Also improved is the display of video metadata when selecting a feed item or entering a video ID in the quick look bar.  Previously this would immediately start a download of the video, but I prefer knowing more about the video first.  These downloads aren&amp;rsquo;t free, and they usually take many hours to get.  Better to know more about them before committing to it.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/40ca90a92c.jpg&#34; width=&#34;600&#34; height=&#34;569&#34; alt=&#34;Video details page.&#34; /&gt;
&lt;p&gt;Incidentally, I think this mode of watching has a slight benefit.  There are days when I spend the whole evening binging YouTube, not so much following the algorithm but looking at the various channels I&amp;rsquo;m interested in for videos that I haven&amp;rsquo;t seen yet.  Waiting several hours for a video download feels a little more measured, and less likely to send me down the YouTube rabbit hole.  I&amp;rsquo;m sure there will still be evenings when I do nothing else other than watch TV, but hopefully that&amp;rsquo;s more of a choice rather than an accident.&lt;/p&gt;
&lt;p&gt;I think this is enough on Broadtail for the time being.  It&amp;rsquo;s more or less functional for what I want to do with it.  Time to move onto something else.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Some Screenshots Of Broadtail</title>
      <link>https://lmika.org/2021/10/31/some-screenshots-of.html</link>
      <pubDate>Sun, 31 Oct 2021 13:09:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/10/31/some-screenshots-of.html</guid>
      <description>&lt;p&gt;I spent some time this morning doing some styling work on Broadtail, my silly little YouTube video download manager I&amp;rsquo;m working on.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/1fd10c6799.jpg&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;Now, I think it&amp;rsquo;s fair to say that I&amp;rsquo;m not a designer.  And these designs look a little dated, but, surprisingly, this is sort of the design I&amp;rsquo;m going for: centered pages, borders, etc.  A bit of a retro, tasteless style that may be ugly, but still usable(-ish).&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2023/e0eb3d3bd3.jpg&#34; width=&#34;600&#34; height=&#34;455&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;It&amp;rsquo;s not quite finished — the colours need a bit of work — but it&amp;rsquo;s sort of the style I have in my head.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2021/10/24/on-a-bit.html</link>
      <pubDate>Sun, 24 Oct 2021 11:46:14 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/10/24/on-a-bit.html</guid>
      <description>&lt;p&gt;On a bit of a writing streak: 50 consecutive days of at least one blog post or journal entry.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2021/0830e4716c.jpg&#34; width=&#34;469&#34; height=&#34;382&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;P.S. I wonder if writing an entry about this streak, just to keep the streak going, is a form of cheating.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2021/10/21/oh-the-irony.html</link>
      <pubDate>Thu, 21 Oct 2021 16:28:00 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/10/21/oh-the-irony.html</guid>
      <description>&lt;p&gt;Oh the irony.  First weekend after Lockdown 6 and with plans for activities outdoors&amp;hellip;&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2021/4acec248fe.png&#34; width=&#34;600&#34; height=&#34;323&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2021/10/05/one-week-to.html</link>
      <pubDate>Tue, 05 Oct 2021 07:36:20 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/10/05/one-week-to.html</guid>
      <description>&lt;p&gt;One week to go! 💉💉&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2021/e20f81c3c1.png&#34; width=&#34;499&#34; height=&#34;243&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2021/07/09/trying-my-hand.html</link>
      <pubDate>Fri, 09 Jul 2021 12:39:59 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2021/07/09/trying-my-hand.html</guid>
      <description>&lt;p&gt;Trying my hand at logo design for something I&amp;rsquo;m working on.  I&amp;rsquo;m aiming for something that looks like a mix between a bookmark, the letter D, and something to suggest synchronising stuff.  This is probably the best I&amp;rsquo;ve got so far.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2021/f59202671f.png&#34; width=&#34;192&#34; height=&#34;192&#34; alt=&#34;Attempted logo&#34; /&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://lmika.org/2020/09/23/ah-day-one.html</link>
      <pubDate>Wed, 23 Sep 2020 09:14:46 +1100</pubDate>
      
      <guid>http://lmika.micro.blog/2020/09/23/ah-day-one.html</guid>
      <description>&lt;p&gt;Ah, Day One.  At least let me get through the day first.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/25293/2020/ebda61ca78.png&#34; width=&#34;300&#34; height=&#34;215&#34; alt=&#34;&#34; /&gt;
</description>
    </item>
    
  </channel>
</rss>