<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>JayWilson.zip</title>
  <subtitle>My writings, photos, and videos about anything and everything.</subtitle>
  <link href="https://jaywilson.zip/posts/atom.xml" rel="self" />
  <link href="https://jaywilson.zip/" />
  <updated>2026-03-15T00:00:00Z</updated>
  <id>https://jaywilson.zip/</id>
  <author>
    <name>Jay Wilson</name>
    <email>heyjay@omg.lol</email>
  </author>
  <entry>
    <title>How I Automated JABA&#39;s Developer Update Posts</title>
    <link href="https://jaywilson.zip/posts/2026/03/15-automated-post/" />
    <updated>2026-03-15T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2026/03/15-automated-post/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://jaba.cctplus.dev&quot;&gt;JABA&lt;/a&gt; has three active repos: iOS, backend, and website. Meaningful work ships across all of them every two weeks, and users and newsletter subscribers deserve to know what changed. Consistent content about what&#39;s being built matters for SEO too. The problem was that writing updates kept falling off my plate. I&#39;d miss a cycle, feel behind, and the gap would grow.&lt;/p&gt;
&lt;p&gt;I built a pipeline to fix that. It pulls from all three repos, collects merged PRs and commits, and turns them into a published blog post every two weeks without me writing anything.&lt;/p&gt;
&lt;h2 id=&quot;what-the-pipeline-does&quot;&gt;What the Pipeline Does&lt;/h2&gt;
&lt;p&gt;A GitHub Actions workflow runs on odd-numbered Thursdays. It collects recent merged PRs and commits from all three repos, hands them to Claude with a carefully defined prompt, validates the output, and opens a PR on the Hugo site for my review. I approve it, Cloudflare builds, and the post goes live. Newsletter content falls out the other side automatically.&lt;/p&gt;
&lt;p&gt;Here&#39;s the full flow:&lt;/p&gt;
&lt;pre class=&quot;language-mermaid&quot;&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;flowchart&lt;/span&gt; TD
    A&lt;span class=&quot;token text string&quot;&gt;[GitHub Actions Cron]&lt;/span&gt; &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; B&lt;span class=&quot;token text string&quot;&gt;[Collect Changes]&lt;/span&gt;
    B &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; C&lt;span class=&quot;token text string&quot;&gt;[Generate Post with Claude]&lt;/span&gt;
    C &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; D&lt;span class=&quot;token text string&quot;&gt;{Validate}&lt;/span&gt;
    D &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|Fail|&lt;/span&gt; E&lt;span class=&quot;token text string&quot;&gt;[Pipeline Halts]&lt;/span&gt;
    D &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt;&lt;span class=&quot;token label property&quot;&gt;|Pass|&lt;/span&gt; F&lt;span class=&quot;token text string&quot;&gt;[Open PR for Review]&lt;/span&gt;
    F &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; G&lt;span class=&quot;token text string&quot;&gt;[Approve and Merge]&lt;/span&gt;
    G &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; H&lt;span class=&quot;token text string&quot;&gt;[Cloudflare Build]&lt;/span&gt;
    H &lt;span class=&quot;token arrow operator&quot;&gt;--&gt;&lt;/span&gt; I&lt;span class=&quot;token text string&quot;&gt;[Newsletter Draft Ready]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-five-scripts&quot;&gt;The Five Scripts&lt;/h2&gt;
&lt;p&gt;The pipeline is five Node.js scripts chained with piped I/O. Each reads from stdin and writes to stdout. Every script is independently testable, and failed runs leave intermediate files in &lt;code&gt;/temp&lt;/code&gt; so it&#39;s easy to see exactly where things broke.&lt;/p&gt;
&lt;p&gt;Here&#39;s how the repo is structured:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;automated-dev-updates-newsletter/
  ├── config/
  │   ├── frontmatter-template.yaml
  │   ├── repos.json
  │   └── system-prompt.md
  ├── output/
  │   └── pending-newsletter.md
  ├── scripts/
  │   ├── collect-changes.js
  │   ├── create-pr.js
  │   ├── fetch-newsletter-content.js
  │   ├── generate-post.js
  │   ├── run-pipeline.js
  │   ├── validate-post.js
  │   └── write-hugo-file.js
  ├── state/
  │   └── last-run.json
  └── temp/&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-collect-changes&quot;&gt;1. Collect Changes&lt;/h3&gt;
&lt;p&gt;This script hits the GitHub API via &lt;code&gt;@octokit/rest&lt;/code&gt; and fetches merged PRs from all three repos within the lookback window, plus commits from the iOS repo specifically. Two filtering passes happen here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependency noise removal&lt;/strong&gt;: Anything from Dependabot or matching &lt;code&gt;chore(deps)&lt;/code&gt; patterns gets dropped. Real changes, but meaningless to users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deduplication&lt;/strong&gt;: Commits that belong to a merged PR are already represented by that PR. Including them again would double-count the work and give Claude redundant signal.&lt;/p&gt;
&lt;p&gt;What comes out is a clean JSON object with the actual meaningful changes.&lt;/p&gt;
&lt;h3 id=&quot;2-generate-post&quot;&gt;2. Generate Post&lt;/h3&gt;
&lt;p&gt;The filtered changeset goes into a user message, and the system prompt (which lives in &lt;code&gt;config/system-prompt.md&lt;/code&gt;, not hardcoded) defines JABA&#39;s voice, the output structure, and hard rules about what not to include.&lt;/p&gt;
&lt;p&gt;Claude writes a 300 to 600 word post as a single cohesive narrative, not a bulleted list of changes. The goal is something a non-technical user can read and understand, not a changelog.&lt;/p&gt;
&lt;h3 id=&quot;3-validate&quot;&gt;3. Validate&lt;/h3&gt;
&lt;p&gt;This is the most opinionated part of the system.&lt;/p&gt;
&lt;p&gt;The validator checks five things before the post can proceed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Minimum word count&lt;/strong&gt;: a post that&#39;s too short probably missed something&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No commit SHAs&lt;/strong&gt;: Claude sometimes copies them verbatim from the input&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No PR numbers&lt;/strong&gt;: same problem&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No internal repo names&lt;/strong&gt;: users don&#39;t need to know how the codebase is organized&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No speculative language&lt;/strong&gt;: phrases like &amp;quot;coming soon&amp;quot;, &amp;quot;planned feature&amp;quot;, and &amp;quot;in development&amp;quot; are signs Claude invented roadmap items that don&#39;t exist&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MIN_WORD_COUNT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HALLUCINATION_PHRASES&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;coming soon&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;planned feature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;in the future&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;will be available&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;upcoming feature&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;roadmap&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;we plan to&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;we intend to&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;we will be adding&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;stay tuned&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 1. Minimum word count on body&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wordCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;countWords&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;wordCount &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;MIN_WORD_COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;body is too short (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;wordCount&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; words, minimum is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MIN_WORD_COUNT&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 2. Commit SHA patterns&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shaMatch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;b[0-9a-f]{7,40}&#92;b&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shaMatch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;body contains what looks like a commit SHA: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;shaMatch&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 3. PR number patterns&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prMatch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;#&#92;d+&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prMatch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;body contains a PR/issue number: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;prMatch&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 4. Internal repo names&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; internalNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInternalRepoNames&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reposConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source_repos&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; internalNames&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pattern &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;&#92;b&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[.*+?^${}()|[&#92;]&#92;&#92;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;&#92;$&amp;amp;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;&#92;b&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;i&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;body contains internal repo name: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 5. Hallucination phrases&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bodyLower &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; phrase &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;HALLUCINATION_PHRASES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bodyLower&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;phrase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;body contains hallucination signal phrase: &quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;phrase&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// All checks passed — pass JSON through to stdout&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any failure halts the pipeline entirely. The partial output gets saved to &lt;code&gt;/temp&lt;/code&gt; for debugging, but nothing moves forward. Validation runs before the PR is created, so bad output never reaches reviewers at all.&lt;/p&gt;
&lt;h3 id=&quot;4-write-hugo-file&quot;&gt;4. Write Hugo File&lt;/h3&gt;
&lt;p&gt;The validated post gets wrapped in TOML front matter and saved to the correct path in the Hugo content structure. Date, title, and slug are derived from the run metadata.&lt;/p&gt;
&lt;h3 id=&quot;5-create-pr&quot;&gt;5. Create PR&lt;/h3&gt;
&lt;p&gt;The file is committed to a new branch on the Hugo site repo and a PR is opened with me tagged as reviewer. Nothing publishes automatically. I get a notification, check the post, and can see a live Cloudflare preview of exactly what it&#39;ll look like before approving anything.&lt;/p&gt;
&lt;h2 id=&quot;after-the-merge&quot;&gt;After the Merge&lt;/h2&gt;
&lt;p&gt;When I merge the PR, Cloudflare kicks off a new site build and the post goes live. At the same time, a webhook on the Hugo repo fires a &lt;code&gt;repository_dispatch&lt;/code&gt; event back to the pipeline repo.&lt;/p&gt;
&lt;p&gt;A second GitHub Actions workflow picks that up, strips the TOML front matter from the published file, and writes the clean post body to &lt;code&gt;output/pending-newsletter.md&lt;/code&gt;. Ready to paste into Loops.so with no reformatting needed.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Notify Internal Tools on Changelog PR Merge

&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;closed&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; github.event.pull_request.merged == true &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; startsWith(github.event.pull_request.head.ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&#39;changelog/&#39;)
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest

    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Get merged file path
        &lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;file
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;GH_TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          FILE_PATH=$(gh api &#92;
            repos/$/pulls/$/files &#92;
            --jq &#39;.[0].filename&#39;)
          echo &quot;file_path=$FILE_PATH&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Fire repository_dispatch to jaba&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;internal&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tools
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;GH_TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          jq -n &#92;
            --arg fp &quot;$NaN&quot; &#92;
            &#39;{&quot;event_type&quot;:&quot;changelog-pr-merged&quot;,&quot;client_payload&quot;:{&quot;file_path&quot;:$fp}}&#39; | &#92;
          gh api repos/cctPlus/jaba-internal-tools/dispatches &#92;
            --method POST &#92;
            --input -&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two-phase design is intentional. &lt;code&gt;last-run.json&lt;/code&gt; updates immediately after a successful pipeline run so the next collection window is correct. &lt;code&gt;pending-newsletter.md&lt;/code&gt; only updates after the PR merges, so the newsletter always reflects human-approved content, not just whatever Claude generated.&lt;/p&gt;
&lt;h2 id=&quot;config-driven-not-code-driven&quot;&gt;Config-Driven, Not Code-Driven&lt;/h2&gt;
&lt;p&gt;Adding a new source repo should not require touching pipeline logic. The repo list lives in &lt;code&gt;config/repos.json&lt;/code&gt;. The system prompt lives in &lt;code&gt;config/system-prompt.md&lt;/code&gt;. Adding a new source is a one-line config change.&lt;/p&gt;
&lt;p&gt;The system prompt took iteration to get right. It defines voice, tone, what to include, what to avoid, and explicit output constraints. It&#39;s doing a lot of the work that keeps the output clean, ahead of the validation step.&lt;/p&gt;
&lt;h2 id=&quot;the-cross-repo-webhook-bridge&quot;&gt;The Cross-Repo Webhook Bridge&lt;/h2&gt;
&lt;p&gt;The Hugo site repo can&#39;t directly trigger workflows in the pipeline repo because they&#39;re separate repositories. Rather than share secrets across repos, there&#39;s a minimal workflow on the Hugo site side that fires a &lt;code&gt;repository_dispatch&lt;/code&gt; event on merge. The pipeline repo listens for that event type and handles it.&lt;/p&gt;
&lt;p&gt;Each repo only knows its own side of the contract. No credentials need to be shared across repo boundaries.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The pipeline runs every two weeks whether or not I think about it. I get a PR notification, review a Cloudflare preview, hit merge, and the post is live. The newsletter content is already waiting to be pasted.&lt;/p&gt;
&lt;p&gt;The only step I kept for myself is the approval, and that&#39;s on purpose. Everything else was just friction between shipping and communicating.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to run Swift Tests Serially across files</title>
    <link href="https://jaywilson.zip/posts/2025/11/09-1220-run-swift-tests-serially/" />
    <updated>2025-11-09T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/11/09-1220-run-swift-tests-serially/</id>
    <content type="html">&lt;p&gt;I&#39;m going to list the guide first and then why I needed to do this.&lt;/p&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;p&gt;This is using &lt;a href=&quot;https://developer.apple.com/documentation/testing&quot;&gt;Swift Testing&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;guide&quot;&gt;Guide&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Create a main test Suite and mark it as &lt;code&gt;@Suite(.serialized)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Testing&lt;/span&gt;

&lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MainTestSuite&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Make an extension on the struct and put your other Suite in side it and also mark it as &lt;code&gt;@Suite(.serialized)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MainTestSuite&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubTestSuiteA&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token other-directive property&quot;&gt;#expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repeat as you need to for all your suites&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Run your tests and your tests should ruin serially. In my testing, they are ran in alphabetical order.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;why-i-needed-this&quot;&gt;Why I needed this&lt;/h2&gt;
&lt;p&gt;I am working on FetchKit, a user feedback system for my apps (and maybe yours), and I need to write tests for the repositories, which is what acceses the databases. Since I am testing reading and writing from databases, I want to just use one test database. At the end of each test, I clean up the databse and erase everything so if tests are running parallel the data the test expected to be there could be wiped. &lt;strong&gt;DEFINITELY DO NOT WANT THAT.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A lot of the examples I saw online showed:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Testing&lt;/span&gt;

&lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MainTestSuite&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubTestSuiteA&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token other-directive property&quot;&gt;#expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubTestSuiteB&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token other-directive property&quot;&gt;#expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is fine and ultimately nothing wrong with it, except for the file gets really massive, and I&#39;m not a fan of that. I tried asking AI and well that was a bust, so I just tried writing the &amp;quot;sub&amp;quot; test suites in extensions and it worked.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-fetchkit&quot;&gt;What is FetchKit?&lt;/h2&gt;
&lt;p&gt;FetchKit is a feedback management system that helps developers collect, organize, and act on user feedback directly from their applications. Instead of relying on scattered feedback from app stores, support emails, and social media, FetchKit lets you capture feedback where it matters most: inside your app.&lt;/p&gt;
&lt;p&gt;If you&#39;re interested in FetchKit, then make sure to join the waitlist, and you&#39;ll get weekly updates with the chance to sign up for the beta.&lt;/p&gt;
&lt;style&gt;@import url(&#39;https://fonts.googleapis.com/css2?family=Inter,family=Open+Sans,family=Source+Sans+Pro&amp;display=swap&#39;);&lt;/style&gt;&lt;div class=&quot;newsletter-form-container&quot;&gt;&lt;form class=&quot;newsletter-form&quot; action=&quot;https://app.loops.so/api/newsletter-form/cmebqyzik0aseve0in62w5oj7&quot; method=&quot;POST&quot; style=&quot;display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%;&quot;&gt;&lt;input class=&quot;newsletter-form-input&quot; placeholder=&quot;you@example.com&quot; required=&quot;&quot; type=&quot;email&quot; name=&quot;newsletter-form-input&quot; style=&quot;font-family: &#39;Open Sans&#39;, sans-serif; color: rgb(0, 0, 0); font-size: 14px; margin: 0px 0px 10px; width: 100%; max-width: 300px; min-width: 100px; background: rgb(255, 255, 255); border: 1px solid rgb(209, 213, 219); box-sizing: border-box; box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px; border-radius: 6px; padding: 8px 12px;&quot;&gt;&lt;button type=&quot;submit&quot; class=&quot;newsletter-form-button&quot; style=&quot;background: rgb(210, 255, 31); font-size: 14px; color: rgb(56, 97, 140); font-family: &#39;Source Sans Pro&#39;, sans-serif; display: flex; width: 100%; max-width: 300px; white-space: normal; height: 38px; align-items: center; justify-content: center; flex-direction: row; padding: 9px 17px; box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px; border-radius: 6px; text-align: center; font-style: normal; font-weight: 500; line-height: 20px; border: none; cursor: pointer;&quot;&gt;Join Waitlist&lt;/button&gt;&lt;button type=&quot;button&quot; class=&quot;newsletter-loading-button&quot; style=&quot;background: rgb(210, 255, 31); font-size: 14px; color: rgb(56, 97, 140); font-family: &#39;Source Sans Pro&#39;, sans-serif; display: none; width: 100%; max-width: 300px; white-space: normal; height: 38px; align-items: center; justify-content: center; flex-direction: row; padding: 9px 17px; box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px; border-radius: 6px; text-align: center; font-style: normal; font-weight: 500; line-height: 20px; border: none; cursor: pointer;&quot;&gt;Please wait...&lt;/button&gt;&lt;/form&gt;&lt;div class=&quot;newsletter-success&quot; style=&quot;display: none; align-items: center; justify-content: center; width: 100%;&quot;&gt;&lt;p class=&quot;newsletter-success-message&quot; style=&quot;font-family: &#39;Open Sans&#39;, sans-serif; color: rgb(0, 0, 0); font-size: 17px;&quot;&gt;Thanks for your interest in FetchKit! I&#39;ll send you your first update shortly!&lt;/p&gt;&lt;/div&gt;&lt;div class=&quot;newsletter-error&quot; style=&quot;display: none; align-items: center; justify-content: center; width: 100%;&quot;&gt;&lt;p class=&quot;newsletter-error-message&quot; style=&quot;font-family: &#39;Open Sans&#39;, sans-serif; color: rgb(185, 28, 28); font-size: 17px;&quot;&gt;Oops! Something went wrong, please try again&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;&lt;button class=&quot;newsletter-back-button&quot; type=&quot;button&quot; style=&quot;color:#6b7280;font: 14px, Inter, sans-serif;margin:10px auto;text-align:center;display:none;background:transparent;border:none;cursor:pointer&quot; onmouseout=&quot;this.style.textDecoration=&amp;quot;none&amp;quot;&quot; onmouseover=&quot;this.style.textDecoration=&amp;quot;underline&amp;quot;&quot;&gt;
← Back
&lt;/button&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
function submitHandler(event) {
  event.preventDefault();
  // event.currentTarget is the form element (the element the listener is attached to)
  var form = event.currentTarget;
  var container = form.parentNode;
  var formInput = container.querySelector(&quot;.newsletter-form-input&quot;) || form.querySelector(&quot;.newsletter-form-input&quot;) || form.querySelector(&#39;input[type=&quot;email&quot;]&#39;);
  var success = container.querySelector(&quot;.newsletter-success&quot;);
  var errorContainer = container.querySelector(&quot;.newsletter-error&quot;);
  var errorMessage = container.querySelector(&quot;.newsletter-error-message&quot;);
  var backButton = container.querySelector(&quot;.newsletter-back-button&quot;);
  var submitButton = container.querySelector(&quot;.newsletter-form-button&quot;);
  var loadingButton = container.querySelector(&quot;.newsletter-loading-button&quot;);
  
  if (!formInput) {
    console.error(&quot;Could not find email input&quot;);
    return;
  }

  const rateLimit = () =&gt; {
    errorContainer.style.display = &quot;flex&quot;;
    errorMessage.innerText = &quot;Too many signups, please try again in a little while&quot;;
    submitButton.style.display = &quot;none&quot;;
    formInput.style.display = &quot;none&quot;;
    backButton.style.display = &quot;block&quot;;
  }

  // Compare current time with time of previous sign up
  var time = new Date();
  var timestamp = time.valueOf();
  var previousTimestamp = localStorage.getItem(&quot;loops-form-timestamp&quot;);

  // If last sign up was less than a minute ago
  // display error
  if (previousTimestamp &amp;&amp; Number(previousTimestamp) + 60000 &gt; timestamp) {
    rateLimit();
    return;
  }
  localStorage.setItem(&quot;loops-form-timestamp&quot;, timestamp);

  // Get email value from the input
  var emailValue = formInput.value ? formInput.value.trim() : &quot;&quot;;
  console.log(&quot;Email value captured:&quot;, emailValue); // Debug log
  if (!emailValue) {
    errorContainer.style.display = &quot;flex&quot;;
    errorMessage.innerText = &quot;Please enter an email address.&quot;;
    submitButton.style.display = &quot;flex&quot;;
    loadingButton.style.display = &quot;none&quot;;
    return;
  }

  submitButton.style.display = &quot;none&quot;;
  loadingButton.style.display = &quot;flex&quot;;

  // Build form body - Loops API expects email parameter
  var formBody = &quot;userGroup=FetchKit&amp;mailingLists=cmhs6l3rr3uec0izm5fftcge3&amp;email=&quot; 
    + encodeURIComponent(emailValue)
    ;
  console.log(&quot;Form body being sent:&quot;, formBody); // Debug log

  fetch(form.action, {
    method: &quot;POST&quot;,
    body: formBody,
    headers: {
      &quot;Content-Type&quot;: &quot;application/x-www-form-urlencoded&quot;,
    },
  })
    .then((res) =&gt; [res.ok, res.json(), res])
    .then(([ok, dataPromise, res]) =&gt; {
      if (ok) {
        // If response successful
        // display success
        success.style.display = &quot;flex&quot;;
        form.reset();
      } else {
        // If response unsuccessful
        // display error message or response status
        dataPromise.then(data =&gt; {
          errorContainer.style.display = &quot;flex&quot;;
          errorMessage.innerText = data.message
            ? data.message
            : res.statusText;
        });
      }
    })
    .catch(error =&gt; {
      // check for cloudflare error
      if (error.message === &quot;Failed to fetch&quot;) {
        rateLimit();
        return;
      }
      // If error caught
      // display error message if available
      errorContainer.style.display = &quot;flex&quot;;
      if (error.message) errorMessage.innerText = error.message;
      localStorage.setItem(&quot;loops-form-timestamp&quot;, &#39;&#39;);
    })
    .finally(() =&gt; {
      formInput.style.display = &quot;none&quot;;
      loadingButton.style.display = &quot;none&quot;;
      backButton.style.display = &quot;block&quot;;
    });
}
function resetFormHandler(event) {
  var container = event.target.parentNode;
  var formInput = container.querySelector(&quot;.newsletter-form-input&quot;);
  var success = container.querySelector(&quot;.newsletter-success&quot;);
  var errorContainer = container.querySelector(&quot;.newsletter-error&quot;);
  var errorMessage = container.querySelector(&quot;.newsletter-error-message&quot;);
  var backButton = container.querySelector(&quot;.newsletter-back-button&quot;);
  var submitButton = container.querySelector(&quot;.newsletter-form-button&quot;);

  success.style.display = &quot;none&quot;;
  errorContainer.style.display = &quot;none&quot;;
  errorMessage.innerText = &quot;Oops! Something went wrong, please try again&quot;;
  backButton.style.display = &quot;none&quot;;
  formInput.style.display = &quot;flex&quot;;
  submitButton.style.display = &quot;flex&quot;;
}

var formContainers = document.getElementsByClassName(
  &quot;newsletter-form-container&quot;
);

for (var i = 0; i &lt; formContainers.length; i++) {
  var formContainer = formContainers[i]
  var handlersAdded = formContainer.classList.contains(&#39;newsletter-handlers-added&#39;)
  if (handlersAdded) continue;
  formContainer
    .querySelector(&quot;.newsletter-form&quot;)
    .addEventListener(&quot;submit&quot;, submitHandler);
  formContainer
    .querySelector(&quot;.newsletter-back-button&quot;)
    .addEventListener(&quot;click&quot;, resetFormHandler);
  formContainer.classList.add(&quot;newsletter-handlers-added&quot;);
}
&lt;/script&gt;</content>
  </entry>
  <entry>
    <title>Journey to $1000 MRR: Month 1 recap</title>
    <link href="https://jaywilson.zip/posts/2025/10/31-0001-journey-to-1000mrr/" />
    <updated>2025-10-31T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/10/31-0001-journey-to-1000mrr/</id>
    <content type="html">&lt;p&gt;At the beginning of October, I set out on a lofty goal, &lt;a href=&quot;https://youtu.be/WRvNgOdpxPU?si=GT048Ab_DWMgZHq4&quot;&gt;$1000 MRR&lt;/a&gt; by this time next year (October 2026). I meant to make a blog post, but I forgot in classic Jay fashion.&lt;/p&gt;
&lt;p&gt;My starting stats:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;MRR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iHog&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;$28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camp Notes&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coffee_&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Universe Converter&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;october&quot;&gt;October&lt;/h2&gt;
&lt;p&gt;To help achieve the MRR, I decided to make smaller goals each mopnth that will hopefully help me. For October those were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redesigning and fully implementing RevenueCat paywalls in &lt;a href=&quot;https://ihog.cctplus.dev/&quot;&gt;iHog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Asking for reviews in &lt;a href=&quot;https://campnotes.app&quot;&gt;Camp Notes&lt;/a&gt; and write 3 blog posts to help SEO to the website&lt;/li&gt;
&lt;li&gt;Adding analytics and adjusting the paywall view in Universe Converter&lt;/li&gt;
&lt;li&gt;Adding analytics and RevenueCat to &lt;a href=&quot;https://apps.apple.com/us/app/coffee/id1611166034&quot;&gt;Coffee_&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Get to $50/MRR&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is what I ended up doing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;iHog got exactly what I implemented and I am running a pricing test in the US to see if I can charge a higher price.&lt;/li&gt;
&lt;li&gt;Camp Notes ratings and reviews are being asked for, a new paywall is being experimented with, which you can read &lt;a href=&quot;https://jaywilson.zip/posts/2025/10/15-0821-campnotes-paywall/&quot;&gt;here&lt;/a&gt;. I did not end up writing the 3 blog posts.&lt;/li&gt;
&lt;li&gt;Universe Converter did not get a hard paywall or a RevenueCat paywall because I decided to just keep it as a tip jar. There is not anything I feel comfortable with charging.&lt;/li&gt;
&lt;li&gt;I ended up not working at all on coffee_ 🙃 I ran out of time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What did I end with stats wise?&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;MRR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iHog&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camp Notes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coffee_&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Universe Converter&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I am sitting at $40 MRR, which means I am like $10 short of my goal, but the subscriber numbers increased which is good and the right direction.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;p&gt;Well I started working on a new app &lt;a href=&quot;https://www.threads.com/@heyjaywilson/post/DQMci05kWl8?xmt=AQF0W5ShMAVYmTUbHWTgr7X5sfOA3z49dxmjaEbFQshyig&quot;&gt;JABA&lt;/a&gt; that will take a lot of my focus away from Coffee_ and Universe Converter. I just don&#39;t see those two apps being something I really want to work on long term. JABA is a revamp of PocketBudget that I worked on earlier this year. I just wanted to start from scratch and with no pressure from existing baggage.&lt;/p&gt;
&lt;h4 id=&quot;ihog&quot;&gt;iHog&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Add in a free trial to the monthly product and see if that moves subscriptions&lt;/li&gt;
&lt;li&gt;Figure out what is causing all the crashes&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;camp-notes&quot;&gt;Camp Notes&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Write at least one blog post&lt;/li&gt;
&lt;li&gt;Improve load times on the Campground List&lt;/li&gt;
&lt;li&gt;Start working towards Scout Badges, which are in app achievements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s to November and see if I can keep the needle going up!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Camp Notes&#39;s First Paywall Experiment</title>
    <link href="https://jaywilson.zip/posts/2025/10/15-0821-campnotes-paywall/" />
    <updated>2025-10-16T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/10/15-0821-campnotes-paywall/</id>
    <content type="html">&lt;p&gt;Today, I started my first paywall experiment for &lt;a href=&quot;https://campnotes.app&quot;&gt;Camp Notes&lt;/a&gt;. I don&#39;t really have enough users to make it worth it, but I want to try it out anyway. I am comparing a fixed footer always showing the package buttons with a toggle for a free trial to one long scroll with the free trial option as a button like the non trial options.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2025/10/15-0821-campnotes-paywall/rc-experiment.png&quot; alt=&quot;RevenueCat&#39;s experiment page&quot;&gt;&lt;/p&gt;
&lt;p&gt;What I&#39;m hoping to learn is if users are more likely to convert if they have to scroll or not. I&#39;m also curious if people will switch to the free trial version at all.&lt;/p&gt;
&lt;p&gt;Ideally this experiment is ran until at least 35 users have gone through it, but I think that&#39;ll take a really long time considering I&#39;ve only had 4 users start a trial since I launched on September 6th. If I haven&#39;t hit 35 users by December 15th (2 months from now), then I&#39;ll stop the experiment and choose the option that had slightly better results even if it&#39;s not statistically significant.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2025/10/15-0821-campnotes-paywall/paywall-comparisons.png&quot; alt=&quot;Paywall Comparisons&quot;&gt;&lt;/p&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;The prices in this screen are the placeholders that RevenueCat generates in their app so I can preview the paywalls.&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;This experiment is part of my goal to hit $1000 MRR by October 2026. You can follow that journey on &lt;a href=&quot;https://www.youtube.com/watch?v=WRvNgOdpxPU&amp;amp;list=PLN6lLzliju3Ccvwd_OLEddnUL8hLlv1fQ&quot;&gt;YouTube&lt;/a&gt;, &lt;a href=&quot;https://www.instagram.com/heyjaywilson/&quot;&gt;Instagram&lt;/a&gt;, or right here on my blog.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current MRR: $33&lt;/li&gt;
&lt;li&gt;Goal MRR: $1000&lt;/li&gt;
&lt;li&gt;Months left: 11&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Camp Notes Won a OneSignal Boost Award at Shipaton</title>
    <link href="https://jaywilson.zip/posts/2025/10/13-1241-5thplace/" />
    <updated>2025-10-13T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/10/13-1241-5thplace/</id>
    <content type="html">&lt;p&gt;I entered &lt;a href=&quot;https://campnotes.app/&quot;&gt;Camp Notes&lt;/a&gt; into RevenueCat&#39;s Shipaton hackathon and placed 5th in the OneSignal Boost Award category, winning $5,000 and recognition in their blog post. I&#39;m really excited about this result and glad I decided to participate.&lt;/p&gt;
&lt;p&gt;For context, Camp Notes is a camping app that lets you record details about individual campsites within campgrounds. You can track GPS coordinates, amenities, add photos, and rate your experiences. The idea came from getting into camping this year and wanting something more structured than a shared Apple Note.&lt;/p&gt;
&lt;h2 id=&quot;what-is-shipaton&quot;&gt;What is Shipaton&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.revenuecat.com/blog/company/shipaton-2025-winners/&quot;&gt;Shipaton&lt;/a&gt; is RevenueCat&#39;s hackathon for indie developers. The goal is simple: ship a brand-new app to the iOS, Android, or Mac App Store using the RevenueCat SDK.&lt;/p&gt;
&lt;p&gt;There were multiple award categories including Best Overall App, Build in Public, and category-specific awards for integrations like OneSignal, Adapty, and others. Over 800 entries were submitted, which made the competition pretty intense.&lt;/p&gt;
&lt;h2 id=&quot;the-submission&quot;&gt;The Submission&lt;/h2&gt;
&lt;p&gt;Once I proved Camp Notes was feasible, the entire plan became submitting it to Shipaton. I started development around when the hackathon kicked off, and everything was aimed at having something ready to enter.&lt;/p&gt;
&lt;p&gt;I highlighted the core features in my submission: site-level tracking with GPS coordinates, the 1-5 star rating system, photo documentation, and community data sharing. I also emphasized the collaboration features I was planning to add, which was the original inspiration for building the app.&lt;/p&gt;
&lt;p&gt;I targeted the OneSignal category specifically because I wanted the best chance to win something. I knew notifications and in-app messages would be part of Camp Notes eventually, so implementing OneSignal for the hackathon made sense.&lt;/p&gt;
&lt;h2 id=&quot;using-onesignal&quot;&gt;Using OneSignal&lt;/h2&gt;
&lt;p&gt;OneSignal handles several things in Camp Notes. Initially, I used it to collect emails for the waitlist and notify users when the app launched. Now it powers in-app messages for updates, notifications when subscription trials are ending, and both transactional and marketing emails.&lt;/p&gt;
&lt;p&gt;When a user registers, they get a welcome email encouraging them to use the app again. It&#39;s straightforward stuff, but it works and sets up the foundation for more sophisticated messaging down the road.&lt;/p&gt;
&lt;h2 id=&quot;the-competition&quot;&gt;The Competition&lt;/h2&gt;
&lt;p&gt;Placing 5th out of over 800 entries feels really good. The judging criteria included functionality, design, potential impact, and how well the RevenueCat SDK was integrated, so there was a lot to get right.&lt;/p&gt;
&lt;p&gt;I learned that I should think more strategically about categories if I do this again. The Build in Public award, for example, might have been a better fit if I&#39;d actually planned for it and documented the process more publicly.&lt;/p&gt;
&lt;p&gt;I&#39;d absolutely do this again. The competition pushed me to ship faster and think more carefully about how I could position what I was building.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s Next&lt;/h2&gt;
&lt;p&gt;The app&#39;s roadmap stays the same. I&#39;m focused on visit collaboration, gamification with user rewards, advanced filtering and sorting, and API integration to populate the database with existing campground data.&lt;/p&gt;
&lt;p&gt;I&#39;m aiming for 10+ visits recorded per month by next year and building out the features that make Camp Notes actually useful for people who camp regularly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://www.revenuecat.com/&quot;&gt;RevenueCat&lt;/a&gt;, &lt;a href=&quot;https://onesignal.com/&quot;&gt;OneSignal&lt;/a&gt;, and the other sponsors for running the hackathon. It was a great experience, and I&#39;m really happy with how it turned out.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Camp Notes v1.0.0 is now shipping</title>
    <link href="https://jaywilson.zip/posts/2025/09/12-0740-campnotes-released/" />
    <updated>2025-09-12T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/09/12-0740-campnotes-released/</id>
    <content type="html">&lt;p&gt;After some back and forth, Apple approved Camp Notes 🎉&lt;/p&gt;
&lt;p&gt;The last reason it was not approved was because subscriptions needed an account, which I challenged and then it was approved.&lt;/p&gt;
&lt;p&gt;Here are some stats since launching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloads: 23&lt;/li&gt;
&lt;li&gt;Impressions: 778&lt;/li&gt;
&lt;li&gt;Trials started: 1&lt;/li&gt;
&lt;li&gt;Crashes: 0&lt;/li&gt;
&lt;li&gt;Visits made: 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is how I am interpreting these numbers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My keywords need to be adjusted for more impressions. Let&#39;s be honest &amp;quot;Camp&amp;quot; is probably overloaded, so I need better keywords for better search.&lt;/li&gt;
&lt;li&gt;I need better marketing. I need to write more blog posts on &lt;a href=&quot;https://campnotes.app?utm_campaign=build-in-public&quot;&gt;campnotes.app&lt;/a&gt; to drive more organic content. I need to create social media content to get a wider audience. There&#39;s just a lot of marketing I can do.&lt;/li&gt;
&lt;li&gt;There just isn&#39;t enough campgrounds in the app to entice users to add visits. I prioritized being able to add camp grounds and getting the app out without making sure there were campgrounds in the app that people could add visits to. I think this is making the app seem bare. After v1.1.0 is out, I&#39;m going to work on getting more data into the app.&lt;/li&gt;
&lt;li&gt;It&#39;s been 1 week. It&#39;s been 1 week so I still just need to chill and focus on making a good app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s time to work on RevenueCat&#39;s Shipaton materials though.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Waiting for Review: A Camp Notes Update</title>
    <link href="https://jaywilson.zip/posts/2025/09/03-0915-camp-notes-update/" />
    <updated>2025-09-03T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/09/03-0915-camp-notes-update/</id>
    <content type="html">&lt;p&gt;It&#39;s been 6 App Store submissions, and I still have not gotten past the review process. I have been rejected for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Login and Signup views are not usable on an iPad.&lt;/li&gt;
&lt;li&gt;Legal links in the paywalls didn&#39;t work&lt;/li&gt;
&lt;li&gt;Login is not working properly (3 times)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first 2 submissions were easy mistakes that I should&#39;ve seen coming, but the last one has been like hell trying to debug. I figured out I wasn&#39;t clearing the auth token between login and signup attempts so it would go stale and not allow any logins. I also had TestFligth feedback about the keyboard not showing on the Login view from settings, so I made sure to submit that fix as well. Hopefully 6th time is the charm and I can release on September 15th.&lt;/p&gt;
&lt;p&gt;I still have the waitlist open, so if you&#39;re interested in trying out Camp Notes before it&#39;s released, please sign up &lt;a href=&quot;https://campnotes.app/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m starting to work on v1.1.0 which will include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to add a visit without a campground or site&lt;/li&gt;
&lt;li&gt;Notifications to allow for trial ending and subscription notifications&lt;/li&gt;
&lt;li&gt;What&#39;s new system&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>I finally submitted a side project</title>
    <link href="https://jaywilson.zip/posts/2025/08/27-0626-camp-notes/" />
    <updated>2025-08-27T06:41:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/08/27-0626-camp-notes/</id>
    <content type="html">&lt;p&gt;As you can tell from my &lt;a href=&quot;https://www.youtube.com/@heyjaycodes&quot;&gt;YouTube&lt;/a&gt; channel or post here, I like to start lots of projects, but I am not the best at finishing them. This year &lt;a href=&quot;https://shipaton.com&quot;&gt;Shipaton&lt;/a&gt; was announced and I had big plans to finish &lt;a href=&quot;https://jaywilson.zip/posts/2025/07/10/0110-pbupdate/&quot;&gt;PocketBudget&lt;/a&gt; for it, but I&#39;ll be honest that was way too much work to rush through and finish, so I pivoted to a new project, &lt;a href=&quot;https://campnotes.app&quot;&gt;Camp Notes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Camp Notes started because my wife and I got into camping this year. We wanted to track the campsites we&#39;ve been to, what amenities they had, that kind of stuff. Everyone in the PNW seems to camp so we figured we&#39;d try it out. We could have just used a shared Apple Note, but that would be messy and unstructured. I wanted to capture specific things like ratings and photos showing how much space there is for tents. So I thought &amp;quot;let me see what I can build in 2 weeks.&amp;quot;&lt;/p&gt;
&lt;p&gt;Within those two weeks, I was able to get a basic version of the app done where I could add a campground, a site, and record a visit. I figured I could take it a couple steps further after that to include photos and sharing of a card about the visit. After a few more weeks of development, I had a v1.0.0 release.&lt;/p&gt;
&lt;h2 id=&quot;what-made-this-different&quot;&gt;What Made This Different&lt;/h2&gt;
&lt;p&gt;One thing that really helped me throughout this process was using Claude Code. I leaned on it for designing and architecting features, and it helped me stay focused and move faster.&lt;/p&gt;
&lt;p&gt;The external deadline from Shipaton also really helped me figure out what was and wasn&#39;t necessary. For example, I wanted to add Sign in with Apple support, but I didn&#39;t want to waste time on that since users can use email and password just fine. I made a note to add it later when I implement collaboration features, but it wasn&#39;t needed for v1.&lt;/p&gt;
&lt;h2 id=&quot;staying-focused-on-shipping&quot;&gt;Staying Focused on Shipping&lt;/h2&gt;
&lt;p&gt;Something that really helped was not getting derailed by feedback right before shipping. A couple of days ago I got TestFlight feedback asking why a site number was necessary for logging visits. Instead of completely rethinking and redesigning the add visit flow right before shipping, I just kept the current approach and made a note to work on it later. I didn&#39;t want to rethink entire flows when I was so close to getting something out the door.&lt;/p&gt;
&lt;p&gt;This is something I definitely want to improve since right now the add visit flow can be a bit clunky because you have to create or select the campground and site first before you can log a visit. Recognizing that this could wait for v1.1 instead of blocking my v1.0 launch was really important.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s Next&lt;/h2&gt;
&lt;p&gt;It&#39;s still missing the ability to collaborate on a visit, but that&#39;s the next feature I&#39;ll work on after Apple approves the app. I also want to separate site ratings from visit ratings since right now they&#39;re combined. There&#39;s a bunch of other stuff I want to add like some type of dashboard with stats, trip planning features, the ability to favorite campgrounds, and some nice to haves like campground list sorting and searching.&lt;/p&gt;
&lt;p&gt;For the add visit flow improvements, I&#39;m planning to A/B test a new approach that&#39;s just a form without requiring a campground or site first. I won&#39;t have enough users to get statistically significant results, but I like the idea of practicing this skill for future job interviews.&lt;/p&gt;
&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;/h2&gt;
&lt;p&gt;Having what I feel is enough to submit the app to the App Store is a big deal for completing side projects for me. I am a master at starting but never finishing or getting them out the door. I felt proud when I hit submit, and I&#39;m really proud that I have actually submitted it to the App Store and hopefully others will find it useful.&lt;/p&gt;
&lt;p&gt;The key lessons that helped me actually ship this time: build small versions and get them out, don&#39;t get distracted by things that might not matter, and use external deadlines to help prioritize what&#39;s truly necessary versus nice to have.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PocketBudget Update: July 10, 2025</title>
    <link href="https://jaywilson.zip/posts/2025/07/10/0110-pbupdate/" />
    <updated>2025-07-09T00:01:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/07/10/0110-pbupdate/</id>
    <content type="html">&lt;p&gt;I didn&#39;t really figure out the issue from earlier, but I did find a solution. I just had to use a different formatter. Since, I&#39;m not writing test right now, I had to create a &lt;code&gt;TransactionsListView&lt;/code&gt; to display the list of transactions to make sure they were added correctly. Once that was confirmed, I tweaked the design a bit and now I have this New Transaction view.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2025/07/10/0110-pbupdate/NewTransactionForm.jpg&quot; alt=&quot;New Transaction View&quot;&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m pretty happy with the design, but I still need to figure out some UX around what happens after the transaction is saved or cancelled.&lt;/p&gt;
&lt;p&gt;My next task is to implement the functionality for views to update when data is saved.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PocketBudget Update</title>
    <link href="https://jaywilson.zip/posts/2025/07/09-0746-shipaton-1/" />
    <updated>2025-07-09T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/07/09-0746-shipaton-1/</id>
    <content type="html">&lt;p&gt;Cranked away on PocketBudget last night.&lt;/p&gt;
&lt;p&gt;The last time I &lt;a href=&quot;https://www.youtube.com/live/0V_2YeVDv9c?si=NCpPrdAq5nIm5vjh&quot;&gt;streamed&lt;/a&gt; and worked on it, I ran into a data fetching issue. Turns out my dates were not the same so the comparison in the predicate didn&#39;t work. I made the predicate more flexible by making sure the &lt;code&gt;startDate&lt;/code&gt; are within the same day and not specifically the exact same time and date.&lt;/p&gt;
&lt;p&gt;Started building out the new transaction flow, which is what I&#39;ll continue on stream tonight. Make sure to drop by and say hi either on Twitch or YouTube.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Twitch: &lt;a href=&quot;https://www.twitch.tv/heyjaywilson&quot;&gt;https://www.twitch.tv/heyjaywilson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href=&quot;https://www.youtube.com/@heyjaycodes&quot;&gt;https://www.youtube.com/@heyjaycodes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My goal is to get the iOS app shipped by the end of the month, so I can enter &lt;a href=&quot;https://shipaton.com&quot;&gt;shipaton&lt;/a&gt; and then continue building out the backend.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to see path of file in &quot;Alex: AI for Xcode&quot;</title>
    <link href="https://jaywilson.zip/posts/2025/06/04-1023-alex-hover/" />
    <updated>2025-06-04T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/06/04-1023-alex-hover/</id>
    <content type="html">&lt;p&gt;In &lt;a href=&quot;https://www.alexcodes.app/&quot;&gt;Alex: AI for Xcode&lt;/a&gt;, sometimes you need to add context to the chat and reference a file but sometimes your files are the same name so you can&#39;t really tell which one you actually &lt;strong&gt;need&lt;/strong&gt; to reference.&lt;/p&gt;
&lt;p&gt;To see the full path, hover over the file and the path will appear.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>I love flying</title>
    <link href="https://jaywilson.zip/posts/2025/05/10-0730-love-flying/" />
    <updated>2025-05-10T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/05/10-0730-love-flying/</id>
    <content type="html">&lt;p&gt;Flying is always fun for me. It doesn&#39;t matter where I&#39;m going. Being in an airplane above the land (or water) is always mesmerizing to me.&lt;/p&gt;
&lt;p&gt;I always make sure to book a window seat. I didn&#39;t recently and was quite dissapointed.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PocketBudget Stream #3: Swift Testing and Foreign Keys</title>
    <link href="https://jaywilson.zip/posts/2025/05/01-0608-pocket-budget/" />
    <updated>2025-05-01T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/05/01-0608-pocket-budget/</id>
    <content type="html">&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/j1VlHVkFy8A?si=mbVqUgnRJCKVfjjM&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;This was a long one tonight. Just shy of 3 hours and starting off I didn&#39;t even want to do testing 😬. I also deviated from what I said I was going to do &lt;a href=&quot;https://jaywilson.zip/posts/2025/04/27-1751-pocket-budget/#whats-next&quot;&gt;last time&lt;/a&gt; which was working on a subscriptions table and only worked on what would propel functionality forward, meaning I started working on creating and fetching budgets.&lt;/p&gt;
&lt;h3 id=&quot;creating-the-budget-table&quot;&gt;Creating the budget table&lt;/h3&gt;
&lt;p&gt;Since I had the &lt;code&gt;users&lt;/code&gt; table and repository done, it was actually pretty easy to get the &lt;code&gt;budgets&lt;/code&gt; table created. The tricky part was getting the foreign key relationship right - the &lt;code&gt;budgets&lt;/code&gt; table needs to reference the &lt;code&gt;users&lt;/code&gt; table to maintain data integrity.&lt;/p&gt;
&lt;p&gt;Cursor helped me build the initial statement, but something felt off about it. After some mid-stream googling and checking the PostgreSQL &lt;a href=&quot;https://www.postgresql.org/docs/17/ddl-constraints.html#DDL-CONSTRAINTS-FK&quot;&gt;documentation&lt;/a&gt;, I found out that PostgreSQL has specific requirements for foreign key constraints. Here&#39;s what worked:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;createTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&quot;
    CREATE TABLE IF NOT EXISTS &quot;budgets&quot; (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        user_id UUID REFERENCES &quot;users&quot; (id),
        budget_name TEXT NOT NULL,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL
    )
    &quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;logger
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;REFERENCES &amp;quot;users&amp;quot; (id)&lt;/code&gt; part tells PostgreSQL to make sure any &lt;code&gt;user_id&lt;/code&gt; we try to insert actually exists in the &lt;code&gt;users&lt;/code&gt; table. If we try to create a budget with a non-existent user_id, PostgreSQL will reject it.&lt;/p&gt;
&lt;h3 id=&quot;testing-budget-postgres-actions&quot;&gt;Testing budget postgres actions&lt;/h3&gt;
&lt;p&gt;Testing repositories is usually straightforward with in-memory storage. You just construct the object and pass it into the init. Then you can perform actions, like creating a new one or getting a specific one. Where I ran into issues was testing the Postgres repository.&lt;/p&gt;
&lt;p&gt;I had already implemented tests on the User Repository and knew I needed it to run serialized since all the tests share the same database. What I wasn&#39;t prepared for was how Swift Testing handles multiple test suites. By default, it runs everything in parallel, which means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First test suite starts and creates a user&lt;/li&gt;
&lt;li&gt;Second test suite starts simultaneously and tries to create a user when the table is supposed to be empty&lt;/li&gt;
&lt;li&gt;Both tests try to modify the database at the same time, causing failures&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To solve this, I first tried marking both suites with &lt;code&gt;@Suite(&amp;quot;Budget Repository Tests&amp;quot;, .serialized)&lt;/code&gt;. But that only serializes tests within each suite. The suites themselves were still running in parallel. The &lt;a href=&quot;https://developer.apple.com/documentation/testing/parallelizationtrait&quot;&gt;docs&lt;/a&gt; don&#39;t really cover this case, so I turned to Cursor and the YouTube chat for ideas.&lt;/p&gt;
&lt;p&gt;The solution was to create one parent test suite that controls everything. Here&#39;s what it looks like:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Testing&lt;/span&gt;

&lt;span class=&quot;token attribute atrule&quot;&gt;@testable&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;

&lt;span class=&quot;token attribute atrule&quot;&gt;@Suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Database Tests&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialized&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DatabaseTests&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; userTests&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserPostgresRepositoryTests&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; budgetTests&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BudgetPostgresRepositoryTests&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userTests &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserPostgresRepositoryTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;budgetTests &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BudgetPostgresRepositoryTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Create tables successfully&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testCreateTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; userTests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;testCreateTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; budgetTests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;testCreateTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Create user successfully&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testCreateUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; userTests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;testCreateUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Create budget successfully&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testCreateBudget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; budgetTests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;testCreateBudget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token attribute atrule&quot;&gt;@Test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Fetch budget successfully&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;testFetchBudget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; budgetTests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;testFetchBudget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I took out all the &lt;code&gt;@Test&lt;/code&gt; and &lt;code&gt;@Suite&lt;/code&gt; from the individual repository test files but kept all the actual test logic there. This way, the parent suite controls when tests run, but each repository keeps its own test code organized. I didn&#39;t think this would work at first since I hadn&#39;t seen this pattern in any examples, but it effectively solved the test parallelization issues.&lt;/p&gt;
&lt;h2 id=&quot;whats-next-and-when&quot;&gt;What&#39;s Next and When&lt;/h2&gt;
&lt;p&gt;I&#39;m not too sure when I&#39;ll be back - at most it&#39;ll be about 2 weeks away. I have some personal stuff coming up and it&#39;s causing a delay, but the latest I&#39;ll be back is May 14th at 8pm PST.&lt;/p&gt;
&lt;p&gt;Off stream, I want to get more of the Pockets (AKA categories) figured out, but that will take some time so I&#39;ll more than likely still be doing some of that on stream. If you&#39;re really curious about what I&#39;ll be doing next, you can join the &lt;a href=&quot;https://discord.gg/HD3KjkMPgh&quot;&gt;discord&lt;/a&gt; or &lt;a href=&quot;https://jaywilson.zip/follow&quot;&gt;follow&lt;/a&gt; me on socials.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PocketBudget Stream #2 Recap</title>
    <link href="https://jaywilson.zip/posts/2025/04/27-1751-pocket-budget/" />
    <updated>2025-04-27T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/27-1751-pocket-budget/</id>
    <content type="html">&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/IBWIWOzYGFQ?si=EUI52IhtaJ0sSUDD&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;This was the second stream of me building &lt;a href=&quot;https://jaywilson.zip/posts/2025/04/24-0704-pocket-budget&quot;&gt;&lt;strong&gt;PocketBudget&lt;/strong&gt;&lt;/a&gt;.&lt;br&gt;
I set out to do four things, but only ended up finishing two. Honestly, that&#39;s fine — I learned a lot more about how &lt;a href=&quot;https://github.com/vapor/postgres-nio&quot;&gt;PostgresNIO&lt;/a&gt; works, so still a win.&lt;/p&gt;
&lt;h2 id=&quot;what-happened-this-stream&quot;&gt;What happened this stream?&lt;/h2&gt;
&lt;p&gt;After last stream, I decided I want to tackle all backend needs first before moving on to the client (an iOS app in my case). So this stream, I started working on basic CRUD operations for a &lt;code&gt;User&lt;/code&gt;, since almost all data in the app will eventually relate back to a user.&lt;/p&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;CRUD&lt;/strong&gt; stands for &lt;strong&gt;C&lt;/strong&gt;reate, &lt;strong&gt;R&lt;/strong&gt;ead, &lt;strong&gt;U&lt;/strong&gt;pdate, and &lt;strong&gt;D&lt;/strong&gt;elete.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wanted to complete full CRUD support for users, but only got &lt;code&gt;Create&lt;/code&gt; fully working and started a stub for &lt;code&gt;Read&lt;/code&gt; (fetching all users). Realistically, fetching &lt;em&gt;all&lt;/em&gt; users is only useful for me during development — it doesn’t actually move the app forward much yet.&lt;/p&gt;
&lt;p&gt;One thing I spent time on was learning how to catch a specific error from PostgresNIO. It took some digging, but I found that I needed to check the &lt;code&gt;sqlState&lt;/code&gt; field and match it against the code &lt;code&gt;23505&lt;/code&gt; to detect a unique constraint violation (basically, when trying to insert a user that already exists based on unique fields). Here&#39;s the code I ended up with:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Inside a do-catch block&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; error &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PSQLError&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; sqlState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serverInfo&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sqlState&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sqlState &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;23505&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;🚨 User already exists with the same auth_provider_id and auth_provider&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTTPError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;badRequest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;User with this auth_provider_id and auth_provider already exists.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;🚨 &lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;&#92;(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reflecting&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Rest of catch&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;p&gt;I’ll be continuing some work off stream — mainly converting existing tests from XCTest to Swift Testing and writing new tests for the work done so far. I also plan to finish the &lt;code&gt;Delete&lt;/code&gt; and &lt;code&gt;Update&lt;/code&gt; operations for users offline.&lt;/p&gt;
&lt;p&gt;I want to set up &lt;a href=&quot;https://github.com/swiftlang/swift-format&quot;&gt;swift-format&lt;/a&gt; on the project too, so the codebase stays clean and consistent as it grows.&lt;/p&gt;
&lt;p&gt;For the next stream:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up the &lt;code&gt;subscription&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;ResponseUser&lt;/code&gt; to include whether the user is currently subscribed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keeping the goals small on this stream so that I can hopefully get them all done. If I end up getting them done, then I&#39;ll head on to budgets next.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PocketBudget Stream Recap</title>
    <link href="https://jaywilson.zip/posts/2025/04/24-0704-pocket-budget/" />
    <updated>2025-04-24T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/24-0704-pocket-budget/</id>
    <content type="html">&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/sfkcgzpVoNE?si=SEsF7gJ9sMMG2Hr9&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;what-is-pocketbudget&quot;&gt;What is PocketBudget&lt;/h2&gt;
&lt;p&gt;PocketBudget is an envelope budgeting app. It&#39;s philosphy is to budget every dollar into a pocket (aka category) to be spent.&lt;/p&gt;
&lt;h3 id=&quot;why&quot;&gt;Why?&lt;/h3&gt;
&lt;p&gt;I want to build a budgeting app. I also want to replace my current budgeting app with the one I&#39;m building.&lt;/p&gt;
&lt;h3 id=&quot;whats-the-stack&quot;&gt;What&#39;s the stack?&lt;/h3&gt;
&lt;p&gt;Backend&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Swift&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;iOS App&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SwiftUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More apps to be decided later... I need to finish the app first.&lt;/p&gt;
&lt;h2 id=&quot;goals-from-the-stream&quot;&gt;Goals from the stream&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create backend repo&lt;/li&gt;
&lt;li&gt;Get 1st build of Hummingbird working&lt;/li&gt;
&lt;li&gt;Add Postgresql&lt;/li&gt;
&lt;li&gt;Configure docker to run the database and server&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-happened&quot;&gt;What happened&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Created the backend GitHub repo using the Hummingbird &lt;a href=&quot;https://github.com/hummingbird-project/template&quot;&gt;template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Was able to build and run the server after running configure&lt;/li&gt;
&lt;li&gt;Started to implement &lt;a href=&quot;https://github.com/vapor/fluent&quot;&gt;Fluent&lt;/a&gt; then completely over thought what an &amp;quot;in memory&amp;quot; database meant so pivoted to &lt;a href=&quot;https://github.com/vapor/postgres-nio&quot;&gt;PostgresNIO&lt;/a&gt; to follow the tutorial/guides more on Hummingbird&#39;s website&lt;/li&gt;
&lt;li&gt;Added a &lt;code&gt;docker-compose&lt;/code&gt; file to run the server and database at the same time and was able to see my table in &lt;a href=&quot;https://tableplus.com/&quot;&gt;TablePlus&lt;/a&gt;, the app I use to inspect databases.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;p&gt;TablePlus is part of SetApp, which I&#39;ve found worth the subscription. You can get &lt;a href=&quot;https://lnk.heyjay.coffee/setapp&quot;&gt;SetApp here&lt;/a&gt; and if you sign up we both get a month free.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;p&gt;When: April 27 2024 at 2pm PST
Where: &lt;a href=&quot;https://youtube.com/live/IBWIWOzYGFQ?feature=share&quot;&gt;YouTube&lt;/a&gt; or &lt;a href=&quot;https://lnk.heyjay.coffee/twitch&quot;&gt;Twitch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a user&lt;/li&gt;
&lt;li&gt;Delete a user&lt;/li&gt;
&lt;li&gt;Update a user&#39;s display name&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stretch goals&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Budget model&lt;/li&gt;
&lt;li&gt;Add a budget table&lt;/li&gt;
&lt;li&gt;Create a budget related to a user&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>How I am using AI Assisted Coding</title>
    <link href="https://jaywilson.zip/posts/2025/04/18-1010-AIThoughts/" />
    <updated>2025-04-18T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/18-1010-AIThoughts/</id>
    <content type="html">&lt;p&gt;AI code editors are becoming more popular, and I am trying to incorporate them into my worklfow. The keyword there is trying. It&#39;s successful sometimes and definitely not in other times.&lt;/p&gt;
&lt;p&gt;I&#39;m currently using &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursor&lt;/a&gt; and &lt;a href=&quot;https://www.alexcodes.app/&quot;&gt;Alex&lt;/a&gt; and pay for both. I use both for iOS and Swift projectsm, but Cursor is also used for web projects. Both these products get frequent updates, so these tips could be outdated by the time you see this.&lt;/p&gt;
&lt;p&gt;Last updated: April 24, 2025&lt;/p&gt;
&lt;h2 id=&quot;things-that-work-the-majority-of-the-time&quot;&gt;Things that work (the majority of the time)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Adding more context. Linking a lot of files does allow for more context.&lt;/li&gt;
&lt;li&gt;Creating a full spec and telling it to implement it but to check in occassionally.
&lt;ul&gt;
&lt;li&gt;You still need to really spec out exactly what needs to happen and make sure there are no logic gaps.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Giving as much context as you can think of
&lt;ul&gt;
&lt;li&gt;Make sure to include all the files you think are going to be necessary to implement the task that way it doesn&#39;t make up initializers or function signatures 🙄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;things-that-dont-work-the-majority-of-the-time&quot;&gt;Things that don&#39;t work (the majority of the time)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Using a chat for a lot of different things
&lt;ul&gt;
&lt;li&gt;It&#39;s easy to think that a long running chat will have all the context, but it will forget more things.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Using it or brand new frameworks
&lt;ul&gt;
&lt;li&gt;If there aren&#39;t a lot of articles or resources for the framework you&#39;re trying to use on the internet, the chances of AI helping you with it are really low.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-i-find-ai-really-good-at&quot;&gt;What I find AI really good at&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker files. I am clueless about Docker files, but it&#39;s really good at helping me figure out what I need to change and what I need to do to get it working.&lt;/li&gt;
&lt;li&gt;Scripts. I&#39;m pretty new to making scripts or doing anything with the command line. Since most of that has been written about, AI is actually really good at writing scripts that need to do a specific task.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>iHog v2025.1.0</title>
    <link href="https://jaywilson.zip/posts/2025/04/18-0717-iHog-2025-1-0/" />
    <updated>2025-04-18T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/18-0717-iHog-2025-1-0/</id>
    <content type="html">&lt;p&gt;I&#39;ve released my first release of iHog this year. I wanted this release to do more than a bug fix, but that&#39;s what it is. ETC is releasing a new version of &lt;a href=&quot;https://www.etcconnect.com/About/News/ETC-Unveils-the-Next-Generation-Hog.aspx?LangType=1033&quot;&gt;Hog&lt;/a&gt;, so I need to get the code base ready for potential changes. This meant pausing my &lt;a href=&quot;https://github.com/CCTPlus/ihogApp/issues/39&quot;&gt;Show Notes&lt;/a&gt; feature that I spent way too long trying to get right. I think I did figure out my issue there so it should be able to be added soon after Hog 5 support.&lt;/p&gt;
&lt;h2 id=&quot;user-highlights&quot;&gt;User Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;User profile sheet
&lt;ul&gt;
&lt;li&gt;Allows a user to test new features via a code&lt;/li&gt;
&lt;li&gt;Adds the ability to mass delete objects and shows&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fixes an issue where a user couldn&#39;t delete a show&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;developer-highlights&quot;&gt;Developer Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No more Core Data.
&lt;ul&gt;
&lt;li&gt;This release moves everything over to Swift Data and removes all Core Data related files.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Moved files around in the repo
&lt;ul&gt;
&lt;li&gt;This allows &lt;a href=&quot;https://www.alexcodes.app/&quot;&gt;Alex: AI for Xcode&lt;/a&gt; to work better on packages&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ll make more posts about some of the pains I went through with Swift Data, but for now feel free to check out the code as iHog&#39;s code is available on &lt;a href=&quot;https://github.com/CCTPlus/ihogApp/tree/2025.1.0&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;iHog is built in an open repo, so please feel free to look at the issues, code, or drop me some feature ideas in the &lt;a href=&quot;https://github.com/CCTPlus/ihogApp/discussions&quot;&gt;GitHub Discussion Board&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Things I didn&#39;t think HRT would change #1</title>
    <link href="https://jaywilson.zip/posts/2025/04/08-1440-hrtchange/" />
    <updated>2025-04-08T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/08-1440-hrtchange/</id>
    <content type="html">&lt;p&gt;I started HRT to medically transition in August of 2023. Since then I had the expected changes, small weight distribution and voice change mainly. I had heard that testosterone could change a person&#39;s foot-size but being that I was 29 when I started and fully grown, I didn&#39;t think it would happen to me. I was wrong. Today, I received an order for a new pair of shoes I already owned but 1/2 a size bigger.&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;now&lt;/strong&gt; have 4 pairs of shoes that fit me, a winterized pair of Vans that I bought too big at the time but needed for a trip, a pair of hiking boots, a pair of Thursday boots, and now these &lt;a href=&quot;https://www.reebok.com/products/reebok-club-c-85-vintage-shoes-chalk-paperwhite-glen-green-103162&quot;&gt;Reeboks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I knew my foot had grown in December because my parents gave me the &lt;a href=&quot;https://amzn.to/4iYKiBt&quot;&gt;Thursday boots&lt;/a&gt; as a birthday gift but they were too small when I received them. I just didn&#39;t want to replace my sneakers yet since it&#39;s been too wet to not wear the other shoes. Now that it&#39;s starting to rain a smidge less in the PNW, I&#39;ll probably be making more show purchases.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Writing down my 3 tasks</title>
    <link href="https://jaywilson.zip/posts/2025/04/07-dailyroutine/" />
    <updated>2025-04-07T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/07-dailyroutine/</id>
    <content type="html">&lt;p&gt;Recently, I started writing down my top 3 tasks for the day and it&#39;s a habit that&#39;s sticking Monday through Thursday. I hold myself a bit more accountable when it&#39;s written down than when I have it in a task manager for some reason.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtube.com/shorts/9zVaTKFcQVk?si=VNefIikML4Tn5e7H&quot;&gt;YouTube short&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Sunday Reset</title>
    <link href="https://jaywilson.zip/posts/2025/04/06-Sunday-Reset/" />
    <updated>2025-04-06T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/04/06-Sunday-Reset/</id>
    <content type="html">&lt;p&gt;I let my desk get a bit messy over the last couple of weeks, so it&#39;s time to clean it up. I think I work a bit better when there&#39;s not a lot of clutter and sometimes it&#39;s hard for me to find the difference between clutter and decoration. I can say for sure I had too much clutter with all the papers I recycled today.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://youtube.com/shorts/RD8y_i8i7Hg&quot;&gt;YouTube Short&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trans Day of Visibility 2025</title>
    <link href="https://jaywilson.zip/posts/2025/03/31-trans-day-visibility/" />
    <updated>2025-03-31T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/03/31-trans-day-visibility/</id>
    <content type="html">&lt;p&gt;I don’t talk about this too much here, and I think today is a good day to do it. I’m Jay and I’m a trans man. My pronouns are he/him. I started posting on socials, streaming, and making YouTube content as a way to represent people I didn’t see a lot of in tech and I am going to continue that.&lt;/p&gt;
&lt;p&gt;Today is Trans Day of Visibility which is a day celebrated in the community to just raise awareness about transgender people. We exist and just want to exist without discrimination as most people do. Today is my 2nd Transgender Day of Visibility since figuring myself out, and I’ve just gotten to be a better and happier version of me since, as most trans people do.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New post from FeedCraft</title>
    <link href="https://jaywilson.zip/posts/2025/02/23-2111/" />
    <updated>2025-02-23T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/23-2111/</id>
    <content type="html">&lt;p&gt;Here’s a new post from the FeedCraft app I’m working on.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Callout support</title>
    <link href="https://jaywilson.zip/posts/2025/02/21-0746-callout-support/" />
    <updated>2025-02-21T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/21-0746-callout-support/</id>
    <content type="html">&lt;p&gt;With the help of Cursor, I think I&#39;ve added support for callouts similar to &lt;a href=&quot;https://github.blog/changelog/2023-12-14-new-markdown-extension-alerts-provide-distinctive-styling-for-significant-content/&quot;&gt;GitHub&#39;s alerts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are examples of how mine render:&lt;/p&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;That has one line in it.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote data-callout=&quot;tip&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;TIP&lt;/p&gt;
&lt;p&gt;This is a tip&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote data-callout=&quot;important&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;IMPORTANT&lt;/p&gt;
&lt;p&gt;This is important&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;WARNING&lt;/p&gt;
&lt;p&gt;This is a warning&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote data-callout=&quot;caution&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;CAUTION&lt;/p&gt;
&lt;p&gt;This is a caution&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-label&quot;&gt;&lt;span class=&quot;callout-icon&quot;&gt;&lt;/span&gt;NOTE&lt;/p&gt;
&lt;p&gt;This is a note&lt;/p&gt;&lt;p&gt;With a second paragraph and some inline &lt;strong&gt;styles&lt;/strong&gt; and a &lt;a href=&quot;https://example.com&quot;&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&#39;s how it works under the hood:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; [!NOTE]
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; This is a note
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt;
&lt;span class=&quot;token blockquote punctuation&quot;&gt;&gt;&lt;/span&gt; With a second paragraph and some inline &lt;span class=&quot;token bold&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;styles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;**&lt;/span&gt;&lt;/span&gt; and a &lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;link&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://example.com&lt;/span&gt;)&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;gt; [!NOTE]&lt;/code&gt; is the callout type which is picked up by a plugin that looks for the token and created the HTML structure of the callout.&lt;/p&gt;
&lt;p&gt;That structure looks like&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;blockquote&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-callout&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;note&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;callout-label&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;callout-icon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;NOTE&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This is a note&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;With a second paragraph and some inline &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;styles&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; and a &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://example.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;link&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;blockquote&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The hard part was getting the tokens and the content right underneath the callout type to render correctly.&lt;/p&gt;
&lt;p&gt;Here&#39;s the final javascript that does the trick:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; MarkdownIt &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;markdown-it&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calloutsPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; md &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;markdownLibrary &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MarkdownIt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; md&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  md&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;core&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ruler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;callouts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;blockquote_open&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; match &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;content&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^&#92;[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)&#92;]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// Add data-callout attribute&lt;/span&gt;
          token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;data-callout&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Add class to the first paragraph&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; para &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;para &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; para&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;paragraph_open&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            para&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; para&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            para&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;class&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;callout-label&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Get cleaned content&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Set the label&lt;/span&gt;
          text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;span class=&quot;callout-icon&quot;&gt;&amp;lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;match&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token comment&quot;&gt;// Add paragraph with content&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; paraOpen &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;paragraph_open&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contentToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          contentToken&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; paraClose &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Token&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;paragraph_close&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          tokens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paraOpen&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contentToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paraClose&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This plugin is then added to my 11ty config file and runs on build.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This was quite the exercise to get it right even with the help of Cursor. It&#39;s hard to get Cursor to think of different ways to do the same thing if you don&#39;t prompt it first. I was stuck in a few loops of getting the same answers over and over when I should have been prompting for different ways to do the same thing. I tried going a mainly CSS route, but it wasn&#39;t working so then I had to prompt it to think about how to do it with standard HTML layout which eventually got me to the solution.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Congrats Canada on winning hockey 🏒</title>
    <link href="https://jaywilson.zip/posts/2025/02/20-2042/" />
    <updated>2025-02-20T20:42:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/20-2042/</id>
    <content type="html">&lt;p&gt;Congrats Canada on winning hockey 🏒 today!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What do we do now?</title>
    <link href="https://jaywilson.zip/posts/2025/02/18-2136/" />
    <updated>2025-02-18T21:36:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/18-2136/</id>
    <content type="html">&lt;p&gt;I&#39;m not sure what to do now. I&#39;m scared. I&#39;ve been scared since the election. I moved so we could live in a state that offered us more protections and rights, but with every day and every executive order, I&#39;m not sure how much protection we will have and for how long.&lt;/p&gt;
&lt;p&gt;I am really hoping that something will be done to stop this dictatorship from getting stronger, but that hope is fading with each passing day.&lt;/p&gt;
&lt;p&gt;So I&#39;m asking, now what? What am I supposed to do? How do I continue to exist when the government is shifting so radically and so quickly?&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Nice post about testing in SwiftUI</title>
    <link href="https://jaywilson.zip/posts/2025/02/18-0949/" />
    <updated>2025-02-18T09:49:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/18-0949/</id>
    <content type="html">&lt;p&gt;Jon Reid, &lt;a href=&quot;https://iosdev.space/@qcoding&quot;&gt;@qcoding&lt;/a&gt;, made a good &lt;a href=&quot;https://qualitycoding.org/what-to-test-in-swiftui/&quot;&gt;article&lt;/a&gt; detailing what to test and what not to test in SwiftUI.&lt;/p&gt;
&lt;p&gt;I like the emphasis on testing behavior not appearance. I think this is really important due to how SwiftUI handles refreshes. SwiftUI is going to update a view based on changes made so we need to make sure the behavior is producing the proper changes. We don&#39;t need to actaully test that the button is Blue instead of Red. We need to test did the conditional change and did it change to the value that&#39;s expected. We can visually see in SwiftUI previews that button will change colors based on the values given.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>FeedCraft coming soon!</title>
    <link href="https://jaywilson.zip/posts/2025/02/18-0622/" />
    <updated>2025-02-18T06:23:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/18-0622/</id>
    <content type="html">&lt;p&gt;Over the weekend, I started working on FeedCraft more seriously (I know I said no new projects, but I technically started this last year so it fits in my year of follow-through) because I need a quick way to post here.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-feedcraft&quot;&gt;What is the purpose of FeedCraft?&lt;/h2&gt;
&lt;p&gt;FeedCraft is an iOS app that allows users to make posts on their GitHub hosted blog. All the blog needs to do is support text files with front matter. Usually these are Markdown files, but they can be any file type.&lt;/p&gt;
&lt;h2 id=&quot;why-did-i-build-feedcraft&quot;&gt;Why did I build FeedCraft?&lt;/h2&gt;
&lt;p&gt;I wanted a way to quickly post to my blog when the inspiration struck, so here we are. An app that&#39;s 80% of the way done and it&#39;s getting a blog post to help me finish it.&lt;/p&gt;
&lt;h2 id=&quot;what-features-does-it-have&quot;&gt;What features does it have?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Customize front matter&lt;/li&gt;
&lt;li&gt;Copy and paste your content from the app to wherever you need it&lt;/li&gt;
&lt;li&gt;Apply front matter templates*&lt;/li&gt;
&lt;li&gt;Create, update, and publish files directly to your GitHub*&lt;/li&gt;
&lt;li&gt;Preview your markdown post*&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;*&lt;/code&gt; = paid features&lt;/p&gt;
&lt;p&gt;There are more features cooking, but for right now, this is what I want to launch with. It might not be a lot but it&#39;s a good start. If you have more ideas on what to add here, then please shoot me a message &lt;a href=&quot;https://jaywilson.zip/contact&quot;&gt;somewhere&lt;/a&gt; on the internet.&lt;/p&gt;
&lt;h2 id=&quot;whats-the-tech-stack&quot;&gt;What&#39;s the tech stack?&lt;/h2&gt;
&lt;p&gt;It&#39;s pretty simple for now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SwiftUI&lt;/li&gt;
&lt;li&gt;Swift Data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;m doing a lot on device for now, so no need to really overcomplicate it. Once I add in photo uploading and crossposting to social media platforms, I might need to add in a server but I don&#39;t think that&#39;s necessary right now.&lt;/p&gt;
&lt;h2 id=&quot;when-does-it-launch&quot;&gt;When does it launch?&lt;/h2&gt;
&lt;p&gt;I&#39;ll be opening a TestFlight at the end of the month, but I need to get some of the UI and UX nailed down a bit more. I&#39;m hoping to go live sometime in March depending on feedback.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thanks y&#39;all for reading!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Six main interests</title>
    <link href="https://jaywilson.zip/posts/2025/02/17-0956/" />
    <updated>2025-02-17T10:54:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/17-0956/</id>
    <content type="html">&lt;p&gt;I was asked by a friend to list off my main 6 interests, so I thought that might make a good blog post. These are listed in most to least interested.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Software development. This could be dwindled down to just Swift development, but sometimes I like to cross into web dev.&lt;/li&gt;
&lt;li&gt;Videography and photography. I like cameras and tech, so this was a no brainer. I enjoy taking the photos, recording the video, and the editing process.&lt;/li&gt;
&lt;li&gt;Taylor Swift.&lt;/li&gt;
&lt;li&gt;Off roading. I don&#39;t do it often, but I am interested in it and do look it up quite a bit. Before the administration ruins public lands, I&#39;m hoping to go out when it&#39;s a bit dryer and explore.&lt;/li&gt;
&lt;li&gt;Corgis.&lt;/li&gt;
&lt;li&gt;Hockey. I root for the Seattle Kraken, Minnesota Wild, and Minnesota Frost.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&#39;m pretty confident in this order, but I do have many more interests.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why I Started Drinking Old Fashioneds</title>
    <link href="https://jaywilson.zip/posts/2025/02/15-why-i-started-drinking/" />
    <updated>2025-02-15T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/15-why-i-started-drinking/</id>
    <content type="html">&lt;p&gt;If we go to a restaurant together, I’ll order an old fashioned. It’s a standard drink that most places can make, and I like bourbon, well now I like bourbon. I didn&#39;t like bourbon before.&lt;/p&gt;
&lt;p&gt;I started drinking old fashions mainly because I thought they were “manly”. It’s kind of silly now that I think and write about it, but it’s true and I’m not alone in this idea. I have a friend who says she started drinking beer because “that’s what lesbians do”.&lt;/p&gt;
&lt;p&gt;It&#39;s interesting thinking how certain actions help you feel more connected to your identity due to social norms. It just makes me wonder how society shapes how we see ourselves.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>AWS free tier is pretty great</title>
    <link href="https://jaywilson.zip/posts/2025/02/09T215300/" />
    <updated>2025-02-09T21:53:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/09T215300/</id>
    <content type="html">&lt;p&gt;I&#39;ve been creating a couple AWS Lambdas lately to run some in progress features for this site, and I&#39;ve been pleasantly surprised by the free tier. Everytime I go to estimate the cost, it&#39;s like $0.00...&lt;/p&gt;
&lt;p&gt;Let&#39;s see what happens as I keep building and adding more though.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>My Website is Back with a New Look</title>
    <link href="https://jaywilson.zip/posts/2025/02/01-website-back/" />
    <updated>2025-02-02T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2025/02/01-website-back/</id>
    <content type="html">&lt;p&gt;Here&#39;s my new website. It&#39;s still a bit under construction and rough around the edges, but it&#39;s a start.&lt;/p&gt;
&lt;p&gt;I use this site as a place to write about my experiences and thoughts on my life. I&#39;m also a software engineer, so I&#39;ll be posting about my projects and thoughts on software development.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What I take with me on a weekend away</title>
    <link href="https://jaywilson.zip/posts/2024/09/09-weekend-away/" />
    <updated>2024-09-09T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/09/09-weekend-away/</id>
    <content type="html">&lt;p&gt;Recently, I went on road trip to see my wife&#39;s family. I thought I&#39;d make a list of what I take in my backpack. There was no expectation of me doing any type of work. I thought I might need to do some task management and maybe want to take some photos/video so that&#39;s what I packed for.&lt;/p&gt;
&lt;blockquote class=&quot;instagram-media&quot; data-instgrm-captioned=&quot;&quot; data-instgrm-permalink=&quot;https://www.instagram.com/reel/C_srRJxAf3F/?utm_source=ig_embed&amp;amp;utm_campaign=loading&quot; data-instgrm-version=&quot;14&quot; style=&quot; background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:540px; min-width:326px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&quot;&gt;&lt;div style=&quot;padding:16px;&quot;&gt; &lt;a href=&quot;https://www.instagram.com/reel/C_srRJxAf3F/?utm_source=ig_embed&amp;amp;utm_campaign=loading&quot; style=&quot; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%;&quot; target=&quot;_blank&quot;&gt; &lt;div style=&quot; display: flex; flex-direction: row; align-items: center;&quot;&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 40px; margin-right: 14px; width: 40px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: column; flex-grow: 1; justify-content: center;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 100px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 60px;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;padding: 19% 0;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display:block; height:50px; margin:0 auto 12px; width:50px;&quot;&gt;&lt;svg width=&quot;50px&quot; height=&quot;50px&quot; viewBox=&quot;0 0 60 60&quot; version=&quot;1.1&quot; xmlns=&quot;https://www.w3.org/2000/svg&quot; xmlns:xlink=&quot;https://www.w3.org/1999/xlink&quot;&gt;&lt;g stroke=&quot;none&quot; stroke-width=&quot;1&quot; fill=&quot;none&quot; fill-rule=&quot;evenodd&quot;&gt;&lt;g transform=&quot;translate(-511.000000, -20.000000)&quot; fill=&quot;#000000&quot;&gt;&lt;g&gt;&lt;path d=&quot;M556.869,30.41 C554.814,30.41 553.148,32.076 553.148,34.131 C553.148,36.186 554.814,37.852 556.869,37.852 C558.924,37.852 560.59,36.186 560.59,34.131 C560.59,32.076 558.924,30.41 556.869,30.41 M541,60.657 C535.114,60.657 530.342,55.887 530.342,50 C530.342,44.114 535.114,39.342 541,39.342 C546.887,39.342 551.658,44.114 551.658,50 C551.658,55.887 546.887,60.657 541,60.657 M541,33.886 C532.1,33.886 524.886,41.1 524.886,50 C524.886,58.899 532.1,66.113 541,66.113 C549.9,66.113 557.115,58.899 557.115,50 C557.115,41.1 549.9,33.886 541,33.886 M565.378,62.101 C565.244,65.022 564.756,66.606 564.346,67.663 C563.803,69.06 563.154,70.057 562.106,71.106 C561.058,72.155 560.06,72.803 558.662,73.347 C557.607,73.757 556.021,74.244 553.102,74.378 C549.944,74.521 548.997,74.552 541,74.552 C533.003,74.552 532.056,74.521 528.898,74.378 C525.979,74.244 524.393,73.757 523.338,73.347 C521.94,72.803 520.942,72.155 519.894,71.106 C518.846,70.057 518.197,69.06 517.654,67.663 C517.244,66.606 516.755,65.022 516.623,62.101 C516.479,58.943 516.448,57.996 516.448,50 C516.448,42.003 516.479,41.056 516.623,37.899 C516.755,34.978 517.244,33.391 517.654,32.338 C518.197,30.938 518.846,29.942 519.894,28.894 C520.942,27.846 521.94,27.196 523.338,26.654 C524.393,26.244 525.979,25.756 528.898,25.623 C532.057,25.479 533.004,25.448 541,25.448 C548.997,25.448 549.943,25.479 553.102,25.623 C556.021,25.756 557.607,26.244 558.662,26.654 C560.06,27.196 561.058,27.846 562.106,28.894 C563.154,29.942 563.803,30.938 564.346,32.338 C564.756,33.391 565.244,34.978 565.378,37.899 C565.522,41.056 565.552,42.003 565.552,50 C565.552,57.996 565.522,58.943 565.378,62.101 M570.82,37.631 C570.674,34.438 570.167,32.258 569.425,30.349 C568.659,28.377 567.633,26.702 565.965,25.035 C564.297,23.368 562.623,22.342 560.652,21.575 C558.743,20.834 556.562,20.326 553.369,20.18 C550.169,20.033 549.148,20 541,20 C532.853,20 531.831,20.033 528.631,20.18 C525.438,20.326 523.257,20.834 521.349,21.575 C519.376,22.342 517.703,23.368 516.035,25.035 C514.368,26.702 513.342,28.377 512.574,30.349 C511.834,32.258 511.326,34.438 511.181,37.631 C511.035,40.831 511,41.851 511,50 C511,58.147 511.035,59.17 511.181,62.369 C511.326,65.562 511.834,67.743 512.574,69.651 C513.342,71.625 514.368,73.296 516.035,74.965 C517.703,76.634 519.376,77.658 521.349,78.425 C523.257,79.167 525.438,79.673 528.631,79.82 C531.831,79.965 532.853,80.001 541,80.001 C549.148,80.001 550.169,79.965 553.369,79.82 C556.562,79.673 558.743,79.167 560.652,78.425 C562.623,77.658 564.297,76.634 565.965,74.965 C567.633,73.296 568.659,71.625 569.425,69.651 C570.167,67.743 570.674,65.562 570.82,62.369 C570.966,59.17 571,58.147 571,50 C571,41.851 570.966,40.831 570.82,37.631&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div style=&quot;padding-top: 8px;&quot;&gt; &lt;div style=&quot; color:#3897f0; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:550; line-height:18px;&quot;&gt;View this post on Instagram&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;padding: 12.5% 0;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: row; margin-bottom: 14px; align-items: center;&quot;&gt;&lt;div&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(0px) translateY(7px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot;background-color: #F4F4F4; height: 12.5px; transform: rotate(-45deg) translateX(3px) translateY(1px); width: 12.5px; flex-grow: 0; margin-right: 14px; margin-left: 2px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot;background-color: #F4F4F4; border-radius: 50%; height: 12.5px; width: 12.5px; transform: translateX(9px) translateY(-18px);&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;margin-left: 8px;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 50%; flex-grow: 0; height: 20px; width: 20px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; width: 0; height: 0; border-top: 2px solid transparent; border-left: 6px solid #f4f4f4; border-bottom: 2px solid transparent; transform: translateX(16px) translateY(-4px) rotate(30deg)&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;margin-left: auto;&quot;&gt; &lt;div style=&quot; width: 0px; border-top: 8px solid #F4F4F4; border-right: 8px solid transparent; transform: translateY(16px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; flex-grow: 0; height: 12px; width: 16px; transform: translateY(-4px);&quot;&gt;&lt;/div&gt; &lt;div style=&quot; width: 0; height: 0; border-top: 8px solid #F4F4F4; border-left: 8px solid transparent; transform: translateY(-4px) translateX(8px);&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; &lt;div style=&quot;display: flex; flex-direction: column; flex-grow: 1; justify-content: center; margin-bottom: 24px;&quot;&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; margin-bottom: 6px; width: 224px;&quot;&gt;&lt;/div&gt; &lt;div style=&quot; background-color: #F4F4F4; border-radius: 4px; flex-grow: 0; height: 14px; width: 144px;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;p style=&quot; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;&quot;&gt;&lt;a href=&quot;https://www.instagram.com/reel/C_srRJxAf3F/?utm_source=ig_embed&amp;amp;utm_campaign=loading&quot; style=&quot; color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;&quot; target=&quot;_blank&quot;&gt;A post shared by Jay (@heyjaywilson)&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://www.instagram.com/embed.js&quot;&gt;&lt;/script&gt;
&lt;h2 id=&quot;the-bag&quot;&gt;The Bag&lt;/h2&gt;
&lt;p&gt;The bag I use is the &lt;a href=&quot;https://shareasale.com/r.cfm?b=1361905&amp;amp;u=3088014&amp;amp;m=79898&amp;amp;urllink=&amp;amp;afftrack=&quot;&gt;Wandrd PRVKE&lt;/a&gt; 21L. I also have this bag in the 31L size, and I really like it. The organization is great and there&#39;s a lot of space.&lt;/p&gt;
&lt;h2 id=&quot;gear&quot;&gt;Gear&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://amzn.to/3ZgE1Ko&quot;&gt;iPad Pro M4 13&amp;quot;&lt;/a&gt; with &lt;a href=&quot;https://amzn.to/47r8RCk&quot;&gt;Apple Pencil Pro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Camera: &lt;a href=&quot;https://amzn.to/3ZegsBV&quot;&gt;RX100v&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Camera: &lt;a href=&quot;https://amzn.to/4gkOWZC&quot;&gt;Sonvy ZVE-1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;AirPods Pro&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;power&quot;&gt;Power&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Anker braided &lt;a href=&quot;https://amzn.to/4d4mPeq&quot;&gt;USB C cables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Anker &lt;a href=&quot;https://amzn.to/4d2Ukh8&quot;&gt;525 charging station&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Anker &lt;a href=&quot;https://amzn.to/4dUv6mw&quot;&gt;3 in 1 power cube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;misc&quot;&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Altoids can with headache medicine&lt;/li&gt;
&lt;li&gt;USB C memory card reader&lt;/li&gt;
&lt;li&gt;USB C hard drive&lt;/li&gt;
&lt;li&gt;MagSafe battery charger&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>FindRelief - Project Log 3</title>
    <link href="https://jaywilson.zip/posts/2024/09/01-find-relief/" />
    <updated>2024-09-01T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/09/01-find-relief/</id>
    <content type="html">&lt;h2 id=&quot;what-i-accomplished-this-week&quot;&gt;What I accomplished this week&lt;/h2&gt;
&lt;p&gt;I didn&#39;t do too much this week, but I did settle on how the lists of places will look. I usually design in code cause it&#39;s easier and quicker, but this was not one of those times. I have a lot of information I want to present in each row, so I decided to turn to work in Sketch and figure out what it should look like.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2024/09/01-find-relief/sketch.png&quot; alt=&quot;Two designs&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are 3 states here.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add a place&lt;/strong&gt;. It doesn&#39;t exist in my database. This is Target in the screenshot. It has the the &lt;code&gt;+&lt;/code&gt; sign.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not selected place&lt;/strong&gt;. It exists in my database, but the user has not selected it to get more info. It&#39;s a lot of icons in the top row, but that&#39;s okay. The user can also upvote/downvote and update the location.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selected place&lt;/strong&gt;. It exists in my database, and the user has selected it. This is Houndstooth in the design on the right. It explains the icons and what the location has restroom wise. The user still has access to upvote/downvote and edit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Again, these are all designs and my intentions. Here&#39;s what I ended up with in SwiftUI previews.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2024/09/01-find-relief/preview.png&quot; alt=&quot;What I ended up coding&quot;&gt;&lt;/p&gt;
&lt;p&gt;The key differences is the size of the arrows, the number of upvotes and downvotes, and the edit button is gone. I made the choice to remove the edit button and replace it with either a swipe action or a context menu, since I don&#39;t think it will be used too often and it wasn&#39;t a crazy thought since &lt;a href=&quot;https://www.threads.net/@steve228uk&quot;&gt;@steve228uk&lt;/a&gt; suggested it on my &lt;a href=&quot;https://www.threads.net/@steve228uk/post/C_V5B7UtLb5?xmt=AQGzJhNNJbxi4nSc08lLiySJEfJ8uMEKAk-7pHkpK8gb1A&quot;&gt;threads post&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Update the data and get the view to adjust in real time. This might take some actual work since I don&#39;t think I really built this in a way to respond to the data like that&lt;/li&gt;
&lt;li&gt;Update the server to retrieve the data&lt;/li&gt;
&lt;li&gt;Update the server to edit for specific properties like upvoting/downvoting&lt;/li&gt;
&lt;li&gt;Implement favorites. Which I think will be the paid part of this app. A small yearly subscription to help cover server costs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;will-i-meet-the-ship-a-ton-deadline&quot;&gt;Will I meet the Ship-a-ton deadline?&lt;/h2&gt;
&lt;p&gt;I don&#39;t think I will and that&#39;s okay. I&#39;ll keep working and trying to though and see how close I get.&lt;/p&gt;
&lt;h2 id=&quot;previous-dev-logs-for-findrelief&quot;&gt;Previous Dev Logs for FindRelief&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://jaywilson.zip/blog/2024/08/13-ship-a-ton&quot;&gt;Log 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jaywilson.zip/blog/2024/08/16-ship-a-ton-log-2&quot;&gt;Log 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>FindRelief - Project Log 2</title>
    <link href="https://jaywilson.zip/posts/2024/08/16-ship-a-ton-log-2/" />
    <updated>2024-08-25T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/08/16-ship-a-ton-log-2/</id>
    <content type="html">&lt;h2 id=&quot;what-i-accomplished-since-log-1&quot;&gt;What I accomplished since &lt;a href=&quot;https://jaywilson.zip/posts/2024/08/16-ship-a-ton-log-2/13-ship-a-ton&quot;&gt;Log 1&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Setup dev vs prod builds using XCConfig files, which I wrote a &lt;a href=&quot;https://heyjaywilson.com/blog/2024/08/18-run-different-builds/&quot;&gt;blog post&lt;/a&gt; for
&lt;img src=&quot;https://share.heyjay.lol/grcTztNoQgy8.png&quot; alt=&quot;Screen shot of both production and dev icons from the simulator&quot;&gt;&lt;/li&gt;
&lt;li&gt;Started using packages due to some reason the previews not working in the Xcode project
&lt;ul&gt;
&lt;li&gt;I should&#39;ve really figured this issue out, but I decided to not worry about it and keep building&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Searching for a place based on a region in a user’s map&lt;/li&gt;
&lt;li&gt;Figure out how to use a Swift Actor to manage the search service
&lt;ul&gt;
&lt;li&gt;I need to still dive into Swift Concurrency a lot more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create a route to add a place on the server&lt;/li&gt;
&lt;li&gt;Allow a user to Add a Place from the app&lt;/li&gt;
&lt;li&gt;Create a &lt;a href=&quot;https://lnk.heyjay.coffee/find-relief-trello&quot;&gt;Trello board&lt;/a&gt; to track tasks and any features/feedback that people might have from seeing various posts on the internet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s a couple posts on social media I made while &lt;s&gt;struggling&lt;/s&gt; building the app.&lt;/p&gt;
&lt;iframe src=&quot;https://iosdev.space/@heyjaywilson/113024316456128907/embed&quot; class=&quot;mastodon-embed&quot; style=&quot;max-width: 100%; border: 0&quot; width=&quot;400&quot; allowfullscreen=&quot;allowfullscreen&quot;&gt;&lt;/iframe&gt;&lt;script src=&quot;https://iosdev.space/embed.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
&lt;blockquote class=&quot;text-post-media&quot; data-text-post-permalink=&quot;https://www.threads.net/@heyjaywilson/post/C_GXSCPxrGG&quot; data-text-post-version=&quot;0&quot; id=&quot;ig-tp-C_GXSCPxrGG&quot; style=&quot; background:#FFF; border-width: 1px; border-style: solid; border-color: #00000026; border-radius: 16px; max-width:540px; margin: 1px; min-width:270px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&quot;&gt; &lt;a href=&quot;https://www.threads.net/@heyjaywilson/post/C_GXSCPxrGG&quot; style=&quot; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%; font-family: -apple-system, BlinkMacSystemFont, sans-serif;&quot; target=&quot;_blank&quot;&gt; &lt;div style=&quot; padding: 40px; display: flex; flex-direction: column; align-items: center;&quot;&gt;&lt;div style=&quot; display:block; height:32px; width:32px; padding-bottom:20px;&quot;&gt; &lt;svg aria-label=&quot;Threads&quot; height=&quot;32px&quot; role=&quot;img&quot; viewBox=&quot;0 0 192 192&quot; width=&quot;32px&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt; &lt;path d=&quot;M141.537 88.9883C140.71 88.5919 139.87 88.2104 139.019 87.8451C137.537 60.5382 122.616 44.905 97.5619 44.745C97.4484 44.7443 97.3355 44.7443 97.222 44.7443C82.2364 44.7443 69.7731 51.1409 62.102 62.7807L75.881 72.2328C81.6116 63.5383 90.6052 61.6848 97.2286 61.6848C97.3051 61.6848 97.3819 61.6848 97.4576 61.6855C105.707 61.7381 111.932 64.1366 115.961 68.814C118.893 72.2193 120.854 76.925 121.825 82.8638C114.511 81.6207 106.601 81.2385 98.145 81.7233C74.3247 83.0954 59.0111 96.9879 60.0396 116.292C60.5615 126.084 65.4397 134.508 73.775 140.011C80.8224 144.663 89.899 146.938 99.3323 146.423C111.79 145.74 121.563 140.987 128.381 132.296C133.559 125.696 136.834 117.143 138.28 106.366C144.217 109.949 148.617 114.664 151.047 120.332C155.179 129.967 155.42 145.8 142.501 158.708C131.182 170.016 117.576 174.908 97.0135 175.059C74.2042 174.89 56.9538 167.575 45.7381 153.317C35.2355 139.966 29.8077 120.682 29.6052 96C29.8077 71.3178 35.2355 52.0336 45.7381 38.6827C56.9538 24.4249 74.2039 17.11 97.0132 16.9405C119.988 17.1113 137.539 24.4614 149.184 38.788C154.894 45.8136 159.199 54.6488 162.037 64.9503L178.184 60.6422C174.744 47.9622 169.331 37.0357 161.965 27.974C147.036 9.60668 125.202 0.195148 97.0695 0H96.9569C68.8816 0.19447 47.2921 9.6418 32.7883 28.0793C19.8819 44.4864 13.2244 67.3157 13.0007 95.9325L13 96L13.0007 96.0675C13.2244 124.684 19.8819 147.514 32.7883 163.921C47.2921 182.358 68.8816 191.806 96.9569 192H97.0695C122.03 191.827 139.624 185.292 154.118 170.811C173.081 151.866 172.51 128.119 166.26 113.541C161.776 103.087 153.227 94.5962 141.537 88.9883ZM98.4405 129.507C88.0005 130.095 77.1544 125.409 76.6196 115.372C76.2232 107.93 81.9158 99.626 99.0812 98.6368C101.047 98.5234 102.976 98.468 104.871 98.468C111.106 98.468 116.939 99.0737 122.242 100.233C120.264 124.935 108.662 128.946 98.4405 129.507Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #999999; font-weight: 400; padding-bottom: 4px; &quot;&gt; Post by @heyjaywilson&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #000000; font-weight: 600; &quot;&gt; View on Threads&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://www.threads.net/embed.js&quot;&gt;&lt;/script&gt;
&lt;blockquote class=&quot;text-post-media&quot; data-text-post-permalink=&quot;https://www.threads.net/@heyjaywilson/post/C_BPDOlRzJ1&quot; data-text-post-version=&quot;0&quot; id=&quot;ig-tp-C_BPDOlRzJ1&quot; style=&quot; background:#FFF; border-width: 1px; border-style: solid; border-color: #00000026; border-radius: 16px; max-width:540px; margin: 1px; min-width:270px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&quot;&gt; &lt;a href=&quot;https://www.threads.net/@heyjaywilson/post/C_BPDOlRzJ1&quot; style=&quot; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%; font-family: -apple-system, BlinkMacSystemFont, sans-serif;&quot; target=&quot;_blank&quot;&gt; &lt;div style=&quot; padding: 40px; display: flex; flex-direction: column; align-items: center;&quot;&gt;&lt;div style=&quot; display:block; height:32px; width:32px; padding-bottom:20px;&quot;&gt; &lt;svg aria-label=&quot;Threads&quot; height=&quot;32px&quot; role=&quot;img&quot; viewBox=&quot;0 0 192 192&quot; width=&quot;32px&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt; &lt;path d=&quot;M141.537 88.9883C140.71 88.5919 139.87 88.2104 139.019 87.8451C137.537 60.5382 122.616 44.905 97.5619 44.745C97.4484 44.7443 97.3355 44.7443 97.222 44.7443C82.2364 44.7443 69.7731 51.1409 62.102 62.7807L75.881 72.2328C81.6116 63.5383 90.6052 61.6848 97.2286 61.6848C97.3051 61.6848 97.3819 61.6848 97.4576 61.6855C105.707 61.7381 111.932 64.1366 115.961 68.814C118.893 72.2193 120.854 76.925 121.825 82.8638C114.511 81.6207 106.601 81.2385 98.145 81.7233C74.3247 83.0954 59.0111 96.9879 60.0396 116.292C60.5615 126.084 65.4397 134.508 73.775 140.011C80.8224 144.663 89.899 146.938 99.3323 146.423C111.79 145.74 121.563 140.987 128.381 132.296C133.559 125.696 136.834 117.143 138.28 106.366C144.217 109.949 148.617 114.664 151.047 120.332C155.179 129.967 155.42 145.8 142.501 158.708C131.182 170.016 117.576 174.908 97.0135 175.059C74.2042 174.89 56.9538 167.575 45.7381 153.317C35.2355 139.966 29.8077 120.682 29.6052 96C29.8077 71.3178 35.2355 52.0336 45.7381 38.6827C56.9538 24.4249 74.2039 17.11 97.0132 16.9405C119.988 17.1113 137.539 24.4614 149.184 38.788C154.894 45.8136 159.199 54.6488 162.037 64.9503L178.184 60.6422C174.744 47.9622 169.331 37.0357 161.965 27.974C147.036 9.60668 125.202 0.195148 97.0695 0H96.9569C68.8816 0.19447 47.2921 9.6418 32.7883 28.0793C19.8819 44.4864 13.2244 67.3157 13.0007 95.9325L13 96L13.0007 96.0675C13.2244 124.684 19.8819 147.514 32.7883 163.921C47.2921 182.358 68.8816 191.806 96.9569 192H97.0695C122.03 191.827 139.624 185.292 154.118 170.811C173.081 151.866 172.51 128.119 166.26 113.541C161.776 103.087 153.227 94.5962 141.537 88.9883ZM98.4405 129.507C88.0005 130.095 77.1544 125.409 76.6196 115.372C76.2232 107.93 81.9158 99.626 99.0812 98.6368C101.047 98.5234 102.976 98.468 104.871 98.468C111.106 98.468 116.939 99.0737 122.242 100.233C120.264 124.935 108.662 128.946 98.4405 129.507Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #999999; font-weight: 400; padding-bottom: 4px; &quot;&gt; Post by @heyjaywilson&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #000000; font-weight: 600; &quot;&gt; View on Threads&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/blockquote&gt;
&lt;blockquote class=&quot;text-post-media&quot; data-text-post-permalink=&quot;https://www.threads.net/@heyjaywilson/post/C-sP60wRjiR&quot; data-text-post-version=&quot;0&quot; id=&quot;ig-tp-C-sP60wRjiR&quot; style=&quot; background:#FFF; border-width: 1px; border-style: solid; border-color: #00000026; border-radius: 16px; max-width:540px; margin: 1px; min-width:270px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);&quot;&gt; &lt;a href=&quot;https://www.threads.net/@heyjaywilson/post/C-sP60wRjiR&quot; style=&quot; background:#FFFFFF; line-height:0; padding:0 0; text-align:center; text-decoration:none; width:100%; font-family: -apple-system, BlinkMacSystemFont, sans-serif;&quot; target=&quot;_blank&quot;&gt; &lt;div style=&quot; padding: 40px; display: flex; flex-direction: column; align-items: center;&quot;&gt;&lt;div style=&quot; display:block; height:32px; width:32px; padding-bottom:20px;&quot;&gt; &lt;svg aria-label=&quot;Threads&quot; height=&quot;32px&quot; role=&quot;img&quot; viewBox=&quot;0 0 192 192&quot; width=&quot;32px&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt; &lt;path d=&quot;M141.537 88.9883C140.71 88.5919 139.87 88.2104 139.019 87.8451C137.537 60.5382 122.616 44.905 97.5619 44.745C97.4484 44.7443 97.3355 44.7443 97.222 44.7443C82.2364 44.7443 69.7731 51.1409 62.102 62.7807L75.881 72.2328C81.6116 63.5383 90.6052 61.6848 97.2286 61.6848C97.3051 61.6848 97.3819 61.6848 97.4576 61.6855C105.707 61.7381 111.932 64.1366 115.961 68.814C118.893 72.2193 120.854 76.925 121.825 82.8638C114.511 81.6207 106.601 81.2385 98.145 81.7233C74.3247 83.0954 59.0111 96.9879 60.0396 116.292C60.5615 126.084 65.4397 134.508 73.775 140.011C80.8224 144.663 89.899 146.938 99.3323 146.423C111.79 145.74 121.563 140.987 128.381 132.296C133.559 125.696 136.834 117.143 138.28 106.366C144.217 109.949 148.617 114.664 151.047 120.332C155.179 129.967 155.42 145.8 142.501 158.708C131.182 170.016 117.576 174.908 97.0135 175.059C74.2042 174.89 56.9538 167.575 45.7381 153.317C35.2355 139.966 29.8077 120.682 29.6052 96C29.8077 71.3178 35.2355 52.0336 45.7381 38.6827C56.9538 24.4249 74.2039 17.11 97.0132 16.9405C119.988 17.1113 137.539 24.4614 149.184 38.788C154.894 45.8136 159.199 54.6488 162.037 64.9503L178.184 60.6422C174.744 47.9622 169.331 37.0357 161.965 27.974C147.036 9.60668 125.202 0.195148 97.0695 0H96.9569C68.8816 0.19447 47.2921 9.6418 32.7883 28.0793C19.8819 44.4864 13.2244 67.3157 13.0007 95.9325L13 96L13.0007 96.0675C13.2244 124.684 19.8819 147.514 32.7883 163.921C47.2921 182.358 68.8816 191.806 96.9569 192H97.0695C122.03 191.827 139.624 185.292 154.118 170.811C173.081 151.866 172.51 128.119 166.26 113.541C161.776 103.087 153.227 94.5962 141.537 88.9883ZM98.4405 129.507C88.0005 130.095 77.1544 125.409 76.6196 115.372C76.2232 107.93 81.9158 99.626 99.0812 98.6368C101.047 98.5234 102.976 98.468 104.871 98.468C111.106 98.468 116.939 99.0737 122.242 100.233C120.264 124.935 108.662 128.946 98.4405 129.507Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #999999; font-weight: 400; padding-bottom: 4px; &quot;&gt; Post by @heyjaywilson&lt;/div&gt; &lt;div style=&quot; font-size: 15px; line-height: 21px; color: #000000; font-weight: 600; &quot;&gt; View on Threads&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/blockquote&gt;</content>
  </entry>
  <entry>
    <title>Why does Xcode need to block me from doing anything while copying shared cache? It takes just enough time that I also get distracted...</title>
    <link href="https://jaywilson.zip/posts/2024/08/18-why-does-xcode-block/" />
    <updated>2024-08-15T07:30:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/08/18-why-does-xcode-block/</id>
    <content type="html">&lt;p&gt;Why does Xcode need to block me from doing anything while copying shared cache? It takes just enough time that I also get distracted...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://share.heyjay.lol/NWGKVHfKPF2u.png&quot; alt=&quot;Screenshot of xcode copying cache symbols&quot;&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to get the current MKCoordinateRegion from a MapCameraPosition</title>
    <link href="https://jaywilson.zip/posts/2024/08/14-null-region-from-camera/" />
    <updated>2024-08-15T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/08/14-null-region-from-camera/</id>
    <content type="html">&lt;p&gt;Goal to achieve: Get the &lt;code&gt;MKCoordinateRegion&lt;/code&gt; of the map I&#39;m looking at.&lt;/p&gt;
&lt;p&gt;What I tried and did not work:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapSearchView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token attribute atrule&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapCameraPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;automatic
	&lt;span class=&quot;token attribute atrule&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mapRegion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MKCoordinateRegion&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;of&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;region&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would always print &lt;code&gt;nil&lt;/code&gt; even though it&#39;s supposed to be the current region.&lt;/p&gt;
&lt;p&gt;What I ended up getting to work:&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapSearchView&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token attribute atrule&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapCameraPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;automatic
	&lt;span class=&quot;token attribute atrule&quot;&gt;@State&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mapRegion&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MKCoordinateRegion&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;some&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onMapCameraChange &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mapCameraUpdateContext &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt;
			mapRegion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mapCameraUpdateContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;region
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had to use the specific &lt;code&gt;onMapCameraChange&lt;/code&gt; to get the region and then update a state variable. I can then use that however I want.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why does Xcode need to block me from doing anything while copying shared cache? It takes just enough time that I also get distracted...</title>
    <link href="https://jaywilson.zip/posts/2024/08/14-xcode-issue/" />
    <updated>2024-08-14T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/08/14-xcode-issue/</id>
    <content type="html">&lt;p&gt;Why does Xcode need to block me from doing anything while copying shared cache? It takes just enough time that I also get distracted...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://share.heyjay.lol/NWGKVHfKPF2u.png&quot; alt=&quot;Screenshot of xcode copying cache symbols&quot;&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>FindRelief - Project Log 1</title>
    <link href="https://jaywilson.zip/posts/2024/08/13-ship-a-ton/" />
    <updated>2024-08-14T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/08/13-ship-a-ton/</id>
    <content type="html">&lt;p&gt;I&#39;m starting this log to help hold myself accountable and to build in public. I&#39;m a big proponent of building in public, and I started this project by being quiet about it. The reason for that was cause I needed to see if I could actually do a lot of what I wanted to, and I can so now it&#39;s time to introduce it and start a log.&lt;/p&gt;
&lt;h2 id=&quot;what-is-findrelief&quot;&gt;What is FindRelief?&lt;/h2&gt;
&lt;p&gt;It&#39;s a restroom finder that has an emphasis on what type of restrooms are available. Being trans, I need to find restrooms that are safe for me to use and usually that means a family or gendered neutral restroom so I decided to make an app for that. This data will be self reported by users (or just me).&lt;/p&gt;
&lt;h2 id=&quot;whats-the-tech-stack&quot;&gt;What&#39;s the tech stack?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;iOS App&lt;/strong&gt;: Written in SwiftUI. When I need local storage, then I&#39;ll probably lean on SwiftData, but I might actually use &lt;a href=&quot;https://github.com/mergesort/Boutique&quot;&gt;Boutique&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Map data&lt;/strong&gt; is provided by MapKit since there&#39;s web data available if I decide to expand this app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User authentication&lt;/strong&gt; is handled with &lt;a href=&quot;https://clerk.com/&quot;&gt;Clerk&lt;/a&gt;. I&#39;ve seen this pop up in the web dev world lately and their iOS SDK is in early beta, so I figured why not try it. If it fails, I can pull in another auth platform, but so far it&#39;s been relatively easy to use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Database&lt;/strong&gt; is a dynamodb table. I&#39;m using a single table design. I wanted something quick and easy. Will see how that goes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server&lt;/strong&gt; is written in Swift using the &lt;a href=&quot;https://github.com/hummingbird-project/hummingbird&quot;&gt;Hummingbird framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;whats-been-done-so-far&quot;&gt;What&#39;s been done so far?&lt;/h2&gt;
&lt;p&gt;I&#39;m building both parts of the app at the same time, so that areas of the server are built as I&#39;m working on that area in the iOS app.&lt;/p&gt;
&lt;h3 id=&quot;server-side&quot;&gt;Server side&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Created an access pattern, entity relationship diagrams, and entity charts&lt;/li&gt;
&lt;li&gt;Create a user&lt;/li&gt;
&lt;li&gt;delete a user&lt;/li&gt;
&lt;li&gt;get a specific user&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I mentioned, Clerk handles user auth, but I need to be able to associate a user in the database with updates and places created, so I wanted my own record of ids, dateCreated, and usernames. I also want to be able to store how many updates a user makes (future thinking here about a user&#39;s reputation on the app), so I needed a place to store that. Here&#39;s the data I think I need about a user so far:&lt;/p&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
erDiagram
	User {
		string userid
		string username
		string createdAt
		string updatesAt
		int numberOfUpdates
	}
&lt;/pre&gt;
&lt;h3 id=&quot;app-side&quot;&gt;App Side&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Implement account creation&lt;/li&gt;
&lt;li&gt;Implement account deletion (requirement by apple)&lt;/li&gt;
&lt;li&gt;Implement account sign out&lt;/li&gt;
&lt;li&gt;Started searching for places&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://jaywilson.zip/posts/2024/08/13-ship-a-ton/13-images/search-map.png&quot; alt=&quot;Map with annotations&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tapping on a place on the map will bring up details about that locations restroom&lt;/li&gt;
&lt;li&gt;The details for a location will appear in search results&lt;/li&gt;
&lt;li&gt;Store the locations details in DynamoDB&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Been using VSCode for some swift stuff and not having to deal with the package.resolved always changing is 🔥</title>
    <link href="https://jaywilson.zip/posts/2024/07/12-vscode-love/" />
    <updated>2024-07-12T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/07/12-vscode-love/</id>
    <content type="html">&lt;p&gt;Been using VSCode for some swift stuff and not having to deal with the package.resolved always changing is 🔥&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to access custom headers in Hummingbird 2</title>
    <link href="https://jaywilson.zip/posts/2024/07/CustomHeradersHummingbird/" />
    <updated>2024-07-08T00:00:00Z</updated>
    <id>https://jaywilson.zip/posts/2024/07/CustomHeradersHummingbird/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/hummingbird-project/hummingbird?tab=readme-ov-file&quot;&gt;Hummingbird&lt;/a&gt; is a server framework written in Swift. I&#39;m using it at work and for a few personal projects. It&#39;s small and quick to build, so it checks the boxes for what I&#39;m trying to do.&lt;/p&gt;
&lt;p&gt;The Hummingbird project is quickly moving to v2.0 (as of writing this RC 2 is out 🎉), and that brought a change with accessing custom headers.
In v1 it looked something like &lt;code&gt;request.headers[&amp;quot;custom-header-name&amp;quot;]&lt;/code&gt;. In v2 it now requires a bit more work thanks to the reliance on &lt;a href=&quot;https://github.com/apple/swift-http-types&quot;&gt;&lt;code&gt;Swift-HTTP-Types&lt;/code&gt;&lt;/a&gt;, but ultimately I think it&#39;s a cleaner call site.&lt;/p&gt;
&lt;p&gt;First, you need to extend &lt;code&gt;HTTPField.name&lt;/code&gt; and add the new header name in there.&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// don&#39;t forget to import HTTPTypes&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTTPTypes&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;extension&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTTPField&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// if you don&#39;t force unwrap here, you&#39;ll need to account for the optional in the next step&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; customHeaderName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;custom-header-name&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can call it in a very similar way to v1.&lt;/p&gt;
&lt;pre class=&quot;language-swift&quot;&gt;&lt;code class=&quot;language-swift&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; headerValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customHeaderName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
</feed>