<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Rob Roy</title>
	<atom:link href="https://www.robert-e-roy.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.robert-e-roy.com</link>
	<description>Apple Platform Developer</description>
	<lastBuildDate>Tue, 09 Jun 2026 19:22:23 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://i0.wp.com/www.robert-e-roy.com/wp-content/uploads/2017/12/cropped-IMG_3585.jpg?fit=32%2C32&#038;ssl=1</url>
	<title>Rob Roy</title>
	<link>https://www.robert-e-roy.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">56422216</site>	<item>
		<title>I Let AI Sort My Game Cam Cards — Here&#8217;s What It Found</title>
		<link>https://www.robert-e-roy.com/i-let-ai-sort-my-game-cam-cards-heres-what-it-found/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Tue, 09 Jun 2026 19:22:19 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2434</guid>

					<description><![CDATA[June 9, 2026 · Rob Roy I have a problem that every trail camera owner knows. Pull the cards, slot them into the Mac, open the folder — and stare down 500 images of wind-blown grass, shadows, and the occasional deer backside. Sorting them manually takes an hour. Most of that hour is deleting blanks. ... <a title="I Let AI Sort My Game Cam Cards — Here&#8217;s What It Found" class="read-more" href="https://www.robert-e-roy.com/i-let-ai-sort-my-game-cam-cards-heres-what-it-found/" aria-label="Read more about I Let AI Sort My Game Cam Cards — Here&#8217;s What It Found">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><em>June 9, 2026 · Rob Roy</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">I have a problem that every trail camera owner knows. Pull the cards, slot them into the Mac, open the folder — and stare down 500 images of wind-blown grass, shadows, and the occasional deer backside. Sorting them manually takes an hour. Most of that hour is deleting blanks.</p>



<p class="wp-block-paragraph">This week I built something to fix that. It&#8217;s called&nbsp;<strong>camtriage</strong>, it runs entirely on my Mac with no cloud API, and on its first real run against my SD cards it sorted 511 images in about three minutes of my actual attention. Here&#8217;s what it found and where I think this is going.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The Problem With Game Cam Data</h2>



<p class="wp-block-paragraph">A motion-triggered camera doesn&#8217;t know the difference between a white-tailed deer and a branch moving in the wind. It fires on both. On a good month in Maine, maybe 15% of images actually contain an animal worth looking at. The rest are false positives — IR triggers from heat shimmer, shadows crossing the lens, a squirrel that was gone before the shutter fired.</p>



<p class="wp-block-paragraph">The standard workflow is manual triage: click through every image, delete the blanks, move the interesting ones into folders by species. It works, but it scales terribly. A serious hobbyist or wildlife researcher running multiple cameras can pull thousands of images a month.</p>



<p class="wp-block-paragraph">I&#8217;d been thinking about this problem since I started working on&nbsp;<a href="https://robroy.online/">HomesteadAI</a>&nbsp;— a suite of privacy-first, on-device AI tools for Mac. The game cam problem felt like a natural fit. Local processing, structured output, sensitive personal data (your property, your wildlife patterns) that has no business going to a cloud API.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The Stack</h2>



<p class="wp-block-paragraph"><strong>camtriage</strong>&nbsp;uses two models:</p>



<p class="wp-block-paragraph"><strong>Google SpeciesNet</strong>&nbsp;— an open-source wildlife classifier trained on over 65 million camera trap images from around the world. It runs as a batch job: hand it a folder, it classifies every image to species level and returns confidence scores. Geographic priors (I set country=USA, state=ME) improve accuracy by down-weighting species not plausible in Maine.</p>



<p class="wp-block-paragraph"><strong>Apple Foundation Models</strong>&nbsp;— for behavioral annotation. Once SpeciesNet identifies a species, Apple&#8217;s on-device language model generates a one-sentence behavioral note: what the animal is likely doing, and whether the timing is unusual. This runs through the new&nbsp;<code>apple-fm-sdk</code>&nbsp;Python package announced at WWDC 2026 this week, completely on-device, no API key.</p>



<p class="wp-block-paragraph">The behavioral annotation uses a grounded species behavior table rather than asking the model to recall facts — which it does unreliably. I tell it that raccoons are nocturnal, then ask it to describe behavior. The model reasons, it doesn&#8217;t recall.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What Three Cards Found</h2>



<p class="wp-block-paragraph">I ran camtriage against three SD cards this afternoon. Combined results:</p>



<p class="wp-block-paragraph"><strong>Card 1</strong>&nbsp;— wildlife corridor camera, deep woods placement:</p>



<pre class="wp-block-code"><code>  white-tailed deer      15
  wild turkey            12
  domestic cat            7   ← all IR night images
  virginia opossum        4
  eastern gray squirrel   3
  northern raccoon        1
</code></pre>



<p class="wp-block-paragraph">467 of 511 images were blank or low-confidence. The tool filtered them automatically.</p>



<p class="wp-block-paragraph"><strong>Card 2</strong>&nbsp;— driveway/trail entrance camera:</p>



<pre class="wp-block-code"><code>  wild turkey            17
  white-tailed deer      11
  human                   5   ← daytime, near driveway
  vehicle                 5   ← daytime
  woodchuck               1
  northern raccoon        1
</code></pre>



<p class="wp-block-paragraph">The human and vehicle detections were correctly skipped — daytime activity near the driveway is normal. If any of those humans had been detected at night, the tool would have flagged them as alerts with a separate folder.</p>



<p class="wp-block-paragraph"><strong>Card 3 data pending</strong>&nbsp;— running now.</p>



<p class="wp-block-paragraph">Two things jumped out immediately. First, the seven domestic cat detections on Card 1 were all IR night images — cats hunting on the same trails as the turkeys and deer. That&#8217;s useful information for a homestead. Second, the two cards tell completely different stories about camera placement. Card 1 is a wildlife corridor. Card 2 is an access point. The species distribution makes that obvious at a glance.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The Accuracy Story</h2>



<p class="wp-block-paragraph">Before building camtriage I tested two approaches against a labeled test set of 56 images across 12 species — deer, fox, bobcat, turkey, raccoon, crow, cat, squirrel, skunk, woodpecker, opossum, birds.</p>



<p class="wp-block-paragraph"><strong>SpeciesNet: 91.1% accuracy.</strong>&nbsp;It correctly identified bobcat (distinguishing it from domestic cat in several cases), got every fox right, and sorted turkeys cleanly even from awkward angles.</p>



<p class="wp-block-paragraph"><strong>Gemma 4 12B (general vision LLM): 38.2% accuracy.</strong>&nbsp;It called foxes &#8220;bears&#8221; repeatedly, labeled raccoons as coyotes, and identified a domestic cat as a dog. Not because it&#8217;s a bad model — it&#8217;s excellent — but because it has never been trained on Maine IR game cam images at dusk. Domain-specific models win decisively for domain-specific tasks.</p>



<p class="wp-block-paragraph">This is one of the core lessons of the HomesteadAI project. The right model for the job beats the largest model every time.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The Alert System</h2>



<p class="wp-block-paragraph">Not all detections are equal. A deer on the trail is interesting. A mountain lion, an unknown dog, or a human on your property at 2am is something you want to know about immediately.</p>



<p class="wp-block-paragraph">camtriage has a two-tier alert system:</p>



<p class="wp-block-paragraph"><strong>Always-alert labels</strong>&nbsp;— mountain lion, black bear, wolf, unknown dog. Any detection at any confidence above threshold copies the image to an&nbsp;<code>_alerts/</code>&nbsp;folder and flags it at the top of the summary.</p>



<p class="wp-block-paragraph"><strong>Context-sensitive alerts</strong>&nbsp;— the more interesting case. A human detected during the day near the driveway camera is normal. A human detected at night is not. The tool reads the EXIF timestamp, computes time-of-day (dawn/daytime/dusk/night), and alerts only when the timing matches a configured rule:</p>



<pre class="wp-block-code"><code>alert_if:
  human:        &#91;night, dawn]   # trespasser
  domestic cat: &#91;night]         # predator behavior
</code></pre>



<p class="wp-block-paragraph">This means the seven IR cat detections from Card 1 would now trigger alerts — cats active at night on wildlife corridors is worth knowing about, especially with ground-nesting turkey in the same area.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What Apple Announced This Week</h2>



<p class="wp-block-paragraph">The timing of this project turns out to be interesting. WWDC 2026 was today. A few things Apple announced matter directly:</p>



<p class="wp-block-paragraph"><strong>Foundation Models is now multimodal.</strong>&nbsp;The on-device model can now accept image input alongside text. Vision annotation — passing the actual game cam image to Apple FM for visual reasoning — requires macOS 27 Golden Gate (shipping fall 2026). Today on macOS 26 Tahoe, only text annotation works. When Golden Gate ships, the behavioral annotation layer gets significantly richer because the model can actually see what it&#8217;s describing.</p>



<p class="wp-block-paragraph"><strong>The&nbsp;<code>LanguageModel</code>&nbsp;protocol.</strong>&nbsp;Apple introduced a standard Swift interface that Apple Foundation Models, Claude, and Gemini all implement. One API, swappable models. For the eventual native Mac version of camtriage, this is the architecture — write against the protocol, let the user choose the model.</p>



<p class="wp-block-paragraph"><strong>macOS Golden Gate drops Intel.</strong>&nbsp;Apple Silicon only from here. Every machine in the HomesteadAI target market has unified memory and a Neural Engine. The hardware assumption that makes local AI viable is now universal.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Where This Goes</h2>



<p class="wp-block-paragraph"><strong>Near term:</strong>&nbsp;Run more real cards. Refine the confidence threshold based on real false positive rates. Add video support — game cams produce companion video clips for the same trigger events as stills, and I&#8217;m handling that by matching timestamps rather than processing video frames directly.</p>



<p class="wp-block-paragraph"><strong>Fall 2026:</strong>&nbsp;When macOS 27 ships, add image input to the Apple FM annotation layer. A behavioral description that can actually see the image will be significantly more useful than one working from species name and time of day alone.</p>



<p class="wp-block-paragraph"><strong>Native Mac app:</strong>&nbsp;The Python prototype validates the concept. The production version is Swift — SpeciesNet replaced by Apple FM vision (or DeepFaune New England, a 97%-accurate classifier specifically trained on northeastern North American species), the annotation layer using the&nbsp;<code>LanguageModel</code>&nbsp;protocol, a species review UI before any images are copied. The target is a HomesteadAI product that any Mac user can run without touching a terminal.</p>



<p class="wp-block-paragraph"><strong>SmartFiler integration:</strong>&nbsp;The underlying pattern — classify, sort, flag anomalies — is exactly what SmartFiler does for documents. The game cam feature slots into SmartFiler as a v2.0 capability once the Apple FM vision layer is available. One app, one purchase, expanding capability.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The Project</h2>



<p class="wp-block-paragraph">camtriage is open source, MIT licensed, and on GitHub. It runs on Mac, Linux, and Windows — the core pipeline is cross-platform, Apple Foundation Models annotation is a Mac enhancement.</p>



<p class="wp-block-paragraph"><strong><a href="https://github.com/robroy/camtriage">github.com/robroy/camtriage</a></strong></p>



<p class="wp-block-paragraph">If you run trail cameras and have a pile of SD cards you&#8217;re dreading, give it a run. The configuration is a single YAML file — set your country and state, adjust the species behavior table for your region, define your alert labels. The defaults are tuned for the northeastern US.</p>



<p class="wp-block-paragraph">Feedback welcome. Particularly interested in hearing from people running cameras in other regions — the species behavior table is currently Maine-centric and needs contributions from the community.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>Rob Roy builds privacy-first, on-device Mac and iOS apps under the&nbsp;<a href="https://robroy.online/">HomesteadAI</a>&nbsp;brand. This post is part of an ongoing series on local AI for practical homestead and outdoor applications.</em></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2434</post-id>	</item>
		<item>
		<title>CorpusKit Studio: Building a RAG Corpus Pipeline Without the Python Tax</title>
		<link>https://www.robert-e-roy.com/corpuskit-studio-building-a-rag-corpus-pipeline-without-the-python-tax/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 14:13:39 +0000</pubDate>
				<category><![CDATA[Mac OS]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2425</guid>

					<description><![CDATA[If you&#8217;ve spent any time building RAG applications, you know the drill. Before your app can do anything useful, you need a pipeline: extract text from PDFs, chunk it, run it through an embedding model, store the vectors somewhere. If you&#8217;re lucky, you&#8217;ve got a Python environment that isn&#8217;t broken. If you&#8217;re not, you&#8217;re debugging ... <a title="CorpusKit Studio: Building a RAG Corpus Pipeline Without the Python Tax" class="read-more" href="https://www.robert-e-roy.com/corpuskit-studio-building-a-rag-corpus-pipeline-without-the-python-tax/" aria-label="Read more about CorpusKit Studio: Building a RAG Corpus Pipeline Without the Python Tax">Read more</a>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">If you&#8217;ve spent any time building RAG applications, you know the drill. Before your app can do anything useful, you need a pipeline: extract text from PDFs, chunk it, run it through an embedding model, store the vectors somewhere. If you&#8217;re lucky, you&#8217;ve got a Python environment that isn&#8217;t broken. If you&#8217;re not, you&#8217;re debugging dependency conflicts at 11pm wondering why you wanted to do this.</p>



<p class="wp-block-paragraph">I built CorpusKit Studio because I wanted to build RAG-powered iOS apps and I didn&#8217;t want a Python pipeline in the loop.</p>



<h2 class="wp-block-heading">What It Does</h2>



<p class="wp-block-paragraph">CorpusKit Studio is a native Mac app. You drag in a PDF. The app extracts the text, chunks it, generates embeddings using an on-device Core ML model, and exports a signed&nbsp;<code>.corpus</code>&nbsp;bundle that any CorpusKit iOS app can consume directly.</p>



<p class="wp-block-paragraph">No Python. No API keys. No data leaving your machine.</p>



<p class="wp-block-paragraph">That last part matters more than it sounds, especially if you&#8217;re working with sensitive documents — legal filings, medical records, proprietary research. The embedding step, which normally means sending your text to an external API, happens entirely on your Mac using Apple&#8217;s Neural Engine.</p>



<h2 class="wp-block-heading">The Technical Decisions Worth Talking About</h2>



<p class="wp-block-paragraph"><strong>PDF extraction via PDFKit</strong></p>



<p class="wp-block-paragraph">The obvious choice for a Mac app, and it mostly just works. The edge case worth knowing: some PDFs are image-based scans with no extractable text layer. CorpusKit Studio checks for this and warns you if the page character count is suspiciously low. OCR is outside scope — I&#8217;m not going to bundle a third-party OCR engine when there are good dedicated tools for that job. Extract the text first, then bring it into the app.</p>



<p class="wp-block-paragraph"><strong>Running MiniLM on Core ML</strong></p>



<p class="wp-block-paragraph">This was the interesting part. MiniLM-L6-v2 is the embedding model that&#8217;s become a standard for RAG applications — small, fast, good quality for retrieval tasks. Getting it running natively on macOS via Core ML meant converting the model and implementing the WordPiece tokenizer in Swift, which doesn&#8217;t exist anywhere off the shelf.</p>



<p class="wp-block-paragraph">The result: embedding a typical PDF runs in seconds on Apple Silicon. The Neural Engine handles it efficiently and the model is identical to what the iOS CorpusKit apps use, which matters for consistency between where you build the corpus and where you query it.</p>



<p class="wp-block-paragraph"><strong>NSDocument architecture</strong></p>



<p class="wp-block-paragraph">I used NSDocument as the foundation rather than building a custom persistence layer from scratch. That decision gave me File &gt; Open Recent, autosave, and document versioning essentially for free. It&#8217;s one of those AppKit decisions that feels like overhead until you realize how much work it saves.</p>



<p class="wp-block-paragraph"><strong>Cosine similarity search via Accelerate</strong></p>



<p class="wp-block-paragraph">No vector database. The corpus sizes I&#8217;m targeting — a few hundred to a few thousand chunks from a typical book or document — don&#8217;t need one. Accelerate&#8217;s vDSP handles cosine similarity over that scale trivially fast. Adding a vector DB dependency would introduce complexity with no real benefit at this scale.</p>



<h2 class="wp-block-heading">The Part That Isn&#8217;t Just Engineering</h2>



<p class="wp-block-paragraph">There&#8217;s a piece of CorpusKit Studio that goes beyond the mechanical pipeline: the curator layer.</p>



<p class="wp-block-paragraph">Raw text extraction treats every chunk as equal. But if you&#8217;ve actually read the document, you know that&#8217;s not true. Some passages are central to the subject. Others are footnotes, boilerplate, chapter openers that don&#8217;t contain useful retrieval content.</p>



<p class="wp-block-paragraph">CorpusKit Studio lets you read the source document inside the app, highlight passages, and rate their importance. Those curator signals travel with the exported corpus. When the iOS app retrieves results, importance ratings can influence ranking. Eventually — as apps accumulate usage data — real retrieval signals (which chunks actually led to good conversations) can feed back into the corpus weighting.</p>



<p class="wp-block-paragraph">The idea is that a corpus curated by someone who actually understands the content should outperform one that was just mechanically chunked. That&#8217;s the bet.</p>



<h2 class="wp-block-heading">It Teaches You How RAG Actually Works</h2>



<p class="wp-block-paragraph">Something I didn&#8217;t fully anticipate: CorpusKit Studio turns out to be a genuinely good way to understand retrieval-augmented generation at a mechanical level.</p>



<p class="wp-block-paragraph">Most developers interact with RAG through an API or a framework that hides the steps. You send text in, you get responses back, and the middle is a black box. That&#8217;s fine for shipping product, but it means you&#8217;re guessing when something doesn&#8217;t work — when results are irrelevant, when the model seems to miss obvious answers, when response quality is inconsistent.</p>



<p class="wp-block-paragraph">CorpusKit Studio makes the whole pipeline visible. You can see exactly how your document gets chunked. You can adjust chunk size and overlap and immediately see how the boundaries change. You can run a query against your corpus and see the ranked results — with cosine similarity scores — before any LLM is involved. That score tells you whether retrieval is finding the right passages. If it isn&#8217;t, you know the problem is in the corpus, not the model.</p>



<p class="wp-block-paragraph">When you highlight a passage and mark it as high importance, you&#8217;re making a decision about signal vs. noise that a pure pipeline never asks you to make. When you run a test query and see a mediocre chunk ranking above a better one, you learn something real about how embedding similarity works — and you can fix it by adjusting your chunking strategy or adding more representative content.</p>



<p class="wp-block-paragraph">If you&#8217;re learning RAG or trying to build intuition about why your retrieval quality varies, building a corpus in CorpusKit Studio is more instructive than reading about it. The feedback loop is immediate and concrete.</p>



<h2 class="wp-block-heading">What&#8217;s Coming</h2>



<p class="wp-block-paragraph">CorpusKit Studio is coming to the Mac App Store. The product page is live now at robroy.online if you want to read more about what it does.</p>



<p class="wp-block-paragraph">If you&#8217;re building RAG applications on Apple platforms and you&#8217;ve been tolerating the Python pipeline, I&#8217;d be curious what your setup looks like. Drop me a line at contact@robroy.online.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>CorpusKit Studio is a Mac app that builds semantic search corpora from PDF documents using on-device Core ML embeddings.&nbsp;<a href="https://robroy.online/corpuskitstudio/">Learn more →</a></em></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2425</post-id>	</item>
		<item>
		<title>Building a Private On-Device AI Companion: What I Learned</title>
		<link>https://www.robert-e-roy.com/building-a-private-on-device-ai-companion-what-i-learned/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Sat, 18 Apr 2026 22:36:10 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[iOS apps]]></category>
		<category><![CDATA[Mac OS]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2423</guid>

					<description><![CDATA[The starting question Rob Roy · robroy.online · April 2026 A few weeks ago I asked a simple question: could I take a small language model and teach it about one specific domain? That question led me through a full modern AI engineering stack — retrieval-augmented generation, embedding models, on-device inference, Core ML conversion, Apple ... <a title="Building a Private On-Device AI Companion: What I Learned" class="read-more" href="https://www.robert-e-roy.com/building-a-private-on-device-ai-companion-what-i-learned/" aria-label="Read more about Building a Private On-Device AI Companion: What I Learned">Read more</a>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">The starting question</h1>



<p class="wp-block-paragraph"><em>Rob Roy · robroy.online · April 2026</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">A few weeks ago I asked a simple question: could I take a small language model and teach it about one specific domain?</p>



<p class="wp-block-paragraph">That question led me through a full modern AI engineering stack — retrieval-augmented generation, embedding models, on-device inference, Core ML conversion, Apple Foundation Models — and eventually to a working iOS app that runs entirely on the device with no network calls. Along the way I changed direction several times, discarded entire architectures, and learned what actually matters versus what just sounds impressive in a roadmap.</p>



<p class="wp-block-paragraph">This is a record of the journey, including the parts where I was wrong.</p>



<h2 class="wp-block-heading">The project</h2>



<p class="wp-block-paragraph">The app is called Big Book via LLM. It provides a private, on-device conversational interface to the 1939 first edition of the Alcoholics Anonymous Big Book — a text that is firmly in the public domain in the United States. The app retrieves relevant passages from the text in response to user queries and generates warm, concise responses grounded in those passages. Every answer cites its source. Nothing leaves the device.</p>



<p class="wp-block-paragraph">The project had three honest goals from the start:</p>



<p class="wp-block-paragraph">Learn the current AI engineering stack deeply enough to be professionally fluent in it. Build something that might serve a community I care about. Produce work that demonstrates real capability, not just familiarity with buzzwords.</p>



<p class="wp-block-paragraph">Commercial scale was never the primary goal. That turned out to matter, because it freed me to make good technical decisions instead of defensive market positioning.</p>



<h2 class="wp-block-heading">Starting with the wrong architecture</h2>



<p class="wp-block-paragraph">My first instinct was to bundle everything into the app. Download a small LLM, index the Big Book corpus, run inference locally, ship it. Privacy would be the pitch. On-device would be the differentiator.</p>



<p class="wp-block-paragraph">That architecture started falling apart almost immediately.</p>



<p class="wp-block-paragraph">The first problem was model size. Phi-4 Mini quantized to Q4 is about 2.3 GB. Apple&#8217;s On-Demand Resource system caps individual asset packs at 512 MB. The workaround was splitting the GGUF file into chunks and downloading both, but the real issue surfaced first: a 2.3 GB download before the user sees any value is a terrible first experience. Most users abandon apps in the first 60 seconds. A progress bar is not an experience.</p>



<p class="wp-block-paragraph">The second problem was memory. A 2.3 GB model needs roughly 3 GB of RAM to load with overhead. iPhones with 4 GB of RAM physically cannot run it. That cut off iPhone 12 and 13 users — a significant slice of exactly the demographic most likely to benefit from this app.</p>



<p class="wp-block-paragraph">The third problem was stability. llama.cpp integration via Swift bindings involved C++ interop, manual KV cache management, and a class of bugs that don&#8217;t exist in pure Swift. I spent days chasing hard crashes before realizing the architecture was fighting the platform.</p>



<p class="wp-block-paragraph">The lesson: elegance on the developer&#8217;s MacBook is not the same as elegance on the user&#8217;s iPhone.</p>



<h2 class="wp-block-heading">Changing direction</h2>



<p class="wp-block-paragraph">I made three architectural changes in quick succession, each one forced by a problem I should have anticipated:</p>



<p class="wp-block-paragraph">Switched from Phi-4 Mini to Gemma 3 1B. Smaller model, more devices supported, smaller download. Response quality for RAG is largely driven by the retrieval layer, not the model&#8217;s raw capabilities — the LLM is primarily framing retrieved passages rather than generating from scratch. A 1 billion parameter model does this task surprisingly well.</p>



<p class="wp-block-paragraph">Replaced llama.cpp with Apple Foundation Models. When iOS 26 shipped with native on-device LLM support, the entire custom inference stack became unnecessary. No download, no GGUF files, no C++ bridge, no ODR splitting. A single Swift API that uses the Neural Engine directly. The tradeoff was setting iOS 26 as the minimum deployment target, which sounds restrictive but according to Apple&#8217;s data covers 66% of all active iPhones and 74% of devices from the last four years. The users I&#8217;d exclude were mostly on hardware that couldn&#8217;t run the app well regardless.</p>



<p class="wp-block-paragraph">Built a Mac companion app to handle corpus preparation. My original workflow used Python scripts that only I could run. That&#8217;s a workflow for exactly one person. I built CorpusKit Studio as a native Mac app that accepts PDFs, chunks them, generates embeddings via Core ML, lets curators highlight important passages, tests retrieval live, and exports a deployable corpus bundle. No terminal required. No Python. Same code patterns as the iOS app, same embedding model, consistent results across both.</p>



<h2 class="wp-block-heading">The RAG architecture that worked</h2>



<p class="wp-block-paragraph">With the right pieces in place, the final architecture is conceptually simple:</p>



<p class="wp-block-paragraph">A corpus is prepared by CorpusKit Studio on a Mac. The Big Book PDF goes in. PDFKit extracts the text. A chunker splits it into overlapping 200-word segments with metadata preserved. A Core ML converted version of all-MiniLM-L6-v2 generates a 384-dimensional semantic embedding for each chunk. Important passages get curator importance weights set via a highlighting interface. The whole thing exports as a bundle containing chunks.json and embeddings.npy.</p>



<p class="wp-block-paragraph">The iOS app bundles this corpus along with the same MiniLM Core ML model. At query time: the user&#8217;s question is expanded using a domain-specific vocabulary dictionary (&#8220;step 4&#8221; becomes &#8220;searching and fearless moral inventory personal housecleaning list resentments fears&#8221;), embedded using the same model that created the corpus, compared against all chunk embeddings via cosine similarity, weighted by chapter and importance, and the top three chunks are retrieved. Those chunks are assembled into a prompt with a tight three-part response structure. Apple Foundation Models streams a warm, brief response. The UI renders the response text alongside passage cards showing the specific cited sentences.</p>



<p class="wp-block-paragraph">End to end, a query takes about 800 milliseconds on an iPhone 15 Pro. No network. No server. No user data leaving the device.</p>



<h2 class="wp-block-heading">Three design decisions I&#8217;d make again</h2>



<p class="wp-block-paragraph">The three-part response structure. Rather than letting the LLM generate whatever it wanted, I constrained responses to: one sentence acknowledging what the person is bringing, one sentence pointing to where the book addresses it, and one question that opens reflection. Under 50 words total. This makes the app feel like a thoughtful companion rather than a chatbot. The text is just connective tissue — the UI renders actual book passages as citation cards.</p>



<p class="wp-block-paragraph">Sentence-level citation extraction. Chunks are 200 words because that&#8217;s what works for retrieval. But showing 200 words as a citation is overwhelming. At display time, I embed each sentence within the retrieved chunk and show only the single most relevant sentence with its page number. &#8220;Resentment is the number one offender. — Big Book, p. 64&#8221; is more useful than a paragraph of context the user didn&#8217;t ask for.</p>



<p class="wp-block-paragraph">Treating authority as a first-class concept. Not every document in a library is equally authoritative. In law, the Constitution outweighs a law review article. In recovery, the Big Book first edition outweighs commentary. CorpusKit Studio lets curators set authority weights per corpus, stamped into every chunk at export time. The iOS app multiplies similarity by authority, so a passage from a primary source wins over a loosely relevant passage from a secondary one. This encodes domain expertise directly into retrieval rather than hoping the model figures it out.</p>



<h2 class="wp-block-heading">Three decisions I got wrong first</h2>



<p class="wp-block-paragraph">I initially wanted the app to rely on a local Flask server for embeddings during development. This worked in the simulator but created a dependency that couldn&#8217;t ship to actual users. I should have prioritized Core ML embedding conversion from day one rather than using it as a placeholder.</p>



<p class="wp-block-paragraph">I tried to bundle the model inline with the app. Apple&#8217;s ODR system and memory constraints made this painful. The real answer was either a smaller model that fits within reasonable constraints, or Apple&#8217;s own Foundation Models when available. Fighting the platform on model delivery was wasted effort.</p>



<p class="wp-block-paragraph">I considered competing with NotebookLM on features. That would have been a losing strategy. Google&#8217;s free, works-everywhere tool wins on raw capability. What my app can offer that theirs cannot is specialization — domain-tuned query expansion, citation-first UI, crisis awareness appropriate for recovery literature, a warm tone deliberately shaped for the community it serves. General tools cannot credibly be all things to all domains. Specificity is defensible.</p>



<h2 class="wp-block-heading">What I learned about RAG</h2>



<p class="wp-block-paragraph">The retrieval layer matters more than the model. A well-tuned retrieval pipeline with a mediocre LLM outperforms a great LLM with naive retrieval. I spent a disproportionate amount of time on chunk size, overlap, chapter weights, query expansion, and importance scoring — and that&#8217;s where most of the quality came from.</p>



<p class="wp-block-paragraph">Domain knowledge encoded as data beats domain knowledge expected from the model. Rather than hoping the LLM knows that &#8220;step 4&#8221; means a moral inventory, I wrote a query expansion dictionary. Rather than hoping it weights the Constitution higher than a law review article, I stamp authority weights directly onto chunks. The model does not need to be an expert in your domain if your retrieval layer already is.</p>



<p class="wp-block-paragraph">Evaluation is the hardest part. It&#8217;s easy to build RAG that returns something. It&#8217;s hard to know whether it returned the right thing. I built a verification script that runs a set of representative queries and prints the retrieved chunks with scores. Watching those scores move as I tuned chunk size and chapter weights was how I actually knew the system was improving. Without that feedback loop, you&#8217;re flying blind.</p>



<h2 class="wp-block-heading">What I learned about shipping on-device AI</h2>



<p class="wp-block-paragraph">iOS 26 and Apple Foundation Models changed the calculus significantly. Before iOS 26, shipping an on-device LLM meant downloading gigabytes and managing memory pressure yourself. After iOS 26, it&#8217;s a Swift API. For anyone building in this space today, that transition is the single most important development.</p>



<p class="wp-block-paragraph">Core ML conversion has sharp edges. The coremltools package has known issues with Python 3.13, requires specific coremltools versions that support various model architectures, and the documentation assumes you already know what you&#8217;re doing. The path that worked for me was Python 3.11, direct PyTorch-to-CoreML conversion skipping ONNX entirely, FP16 precision, and verifying cosine similarity above 0.999 between the PyTorch reference and the converted Core ML output before trusting anything.</p>



<p class="wp-block-paragraph">Privacy is not automatically a competitive advantage but it is table stakes for certain domains. Generic users don&#8217;t care much about where their queries go. Users dealing with recovery, therapy, legal issues, medical information, or any topic they consider sensitive care a great deal. Choosing domains where privacy matters makes on-device architecture valuable. Choosing domains where it doesn&#8217;t makes on-device architecture expensive and complicated with no upside.</p>



<h2 class="wp-block-heading">Where this goes next</h2>



<p class="wp-block-paragraph">CorpusKit Studio is the more interesting long-term product than the iOS app. It&#8217;s infrastructure — a tool for producing deployable RAG corpora from any PDF, with curator controls for importance, authority, and query expansion. The same engine that produces the Big Book corpus can produce a corpus from any closed body of text. Recovery literature, legal documents, medical education, sacred texts, personal libraries.</p>



<p class="wp-block-paragraph">The iOS app is one consumer of the output. The Mac app is the workshop. The corpus bundle is the portable artifact that connects them. A publisher, a clinician, a sponsor, a curator — none of them needs to touch a terminal to produce a high-quality RAG corpus. That accessibility is what turns this from a personal project into something potentially useful to other people.</p>



<p class="wp-block-paragraph">I plan to release the Big Book corpus package publicly under a permissive license. The corpus derivation is my work; the source text is public domain; the resulting index is something any CorpusKit-compatible application can consume. The recovery community should have access to this kind of tool, and one app cannot reach everyone — but a well-made corpus can work inside any tool that knows how to read it.</p>



<h2 class="wp-block-heading">The skills this built</h2>



<p class="wp-block-paragraph">I was explicit with myself at the start that one of the goals was professional skill development. Working through this project end-to-end, I can now credibly speak to:</p>



<p class="wp-block-paragraph">Retrieval-augmented generation architecture: chunk sizing, overlap, embedding model selection, vector similarity search, query expansion, chapter and importance weighting, evaluation methodology.</p>



<p class="wp-block-paragraph">On-device LLM inference: model quantization tradeoffs, memory constraints on mobile devices, llama.cpp integration, Apple Foundation Models, Core ML model conversion from PyTorch, FP16 precision considerations.</p>



<p class="wp-block-paragraph">Production iOS development with AI: SwiftUI architecture, async token streaming, Core ML integration, proper error handling around AI pipelines, the difference between simulator and device behavior, iOS 26 Foundation Models API specifics.</p>



<p class="wp-block-paragraph">Product decisions in the AI space: when to use RAG versus fine-tuning, how to handle crisis scenarios in sensitive domains, the tradeoffs of closed-source versus open models, cloud versus on-device architectures, the actual versus perceived value of privacy-first design.</p>



<p class="wp-block-paragraph">The deeper lesson is that doing this work is different from reading about it. I could have spent the same weeks watching tutorials and finished with less real understanding than I got from hitting actual problems and solving them. The errors — the crashes, the zero-vector embeddings, the Python version mismatches, the Core ML conversion failures, the response length that wouldn&#8217;t shorten — were where the learning lived.</p>



<h2 class="wp-block-heading">A note on direction changes</h2>



<p class="wp-block-paragraph">Looking back, I changed direction in significant ways roughly five times across this project. Each time it felt like a setback. Each time it was actually the right call.</p>



<p class="wp-block-paragraph">Changing direction is often treated as a failure mode. In software it usually is not. The information you have at the start of a project is always incomplete. The information you have three weeks in is substantially better. Refusing to incorporate new information out of commitment to a plan is a worse outcome than revising the plan honestly.</p>



<p class="wp-block-paragraph">The original architecture had Phi-4 Mini running via llama.cpp with ODR-split GGUF files, a Flask embedding server during development, and a Python corpus pipeline that only I could run. The final architecture has Apple Foundation Models, Core ML embeddings, a Mac app that any curator can use, and a corpus format designed for portability. The final version is dramatically better than the original plan — and I could not have arrived at it without starting somewhere and paying attention.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>Rob Roy is an independent iOS developer based in Maine. His work and contact are at robroy.online.</em></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2423</post-id>	</item>
		<item>
		<title>Netsolver: What 37 Years of DevOps Taught Me to Build</title>
		<link>https://www.robert-e-roy.com/netsolver-what-37-years-of-devops-taught-me-to-build/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Sun, 22 Mar 2026 17:27:06 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[iOS apps]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2414</guid>

					<description><![CDATA[After nearly four decades in systems administration and DevOps — including stints at IBM and Disney — I&#8217;ve managed a lot of infrastructure. Racks of servers, sprawling networks, machines spread across time zones. The tools we used were powerful, expensive, and designed for teams. Then I left enterprise. Now I run a home lab with ... <a title="Netsolver: What 37 Years of DevOps Taught Me to Build" class="read-more" href="https://www.robert-e-roy.com/netsolver-what-37-years-of-devops-taught-me-to-build/" aria-label="Read more about Netsolver: What 37 Years of DevOps Taught Me to Build">Read more</a>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading"></h1>



<p class="wp-block-paragraph">After nearly four decades in systems administration and DevOps — including stints at IBM and Disney — I&#8217;ve managed a lot of infrastructure. Racks of servers, sprawling networks, machines spread across time zones. The tools we used were powerful, expensive, and designed for teams.</p>



<p class="wp-block-paragraph">Then I left enterprise. Now I run a home lab with a handful of Linux boxes, a NAS, a Raspberry Pi or two, and a Mac Mini acting as a server. Nothing exotic. But the same problems I had at IBM still show up at 11pm when something goes quietly offline and I&#8217;m nowhere near a desk.</p>



<p class="wp-block-paragraph">I wanted one app on my iPhone that could tell me what was happening on my network. Not a cloud dashboard with per-seat pricing. Not a terminal emulator with no context. Something purpose-built for the person who runs a small fleet of machines they actually own.</p>



<p class="wp-block-paragraph">So I built Netsolver.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">The problem with existing tools</h2>



<p class="wp-block-paragraph">Enterprise RMM tools — NinjaRMM, ConnectWise, Atera — are genuinely excellent if you&#8217;re managing hundreds of endpoints for paying clients. But they start at per-agent pricing that makes no sense for eight machines in your basement. The onboarding alone assumes you have an IT team.</p>



<p class="wp-block-paragraph">On the other end, there are SSH clients. Good ones. I&#8217;ve used them for years. But an SSH client is a blank terminal. It doesn&#8217;t know what&#8217;s on your network, it doesn&#8217;t alert you when something goes down, and it doesn&#8217;t integrate with the rest of your phone.</p>



<p class="wp-block-paragraph">There&#8217;s a gap between &#8220;full enterprise RMM&#8221; and &#8220;just SSH.&#8221; That gap is exactly where most home lab operators and small businesses live. That&#8217;s what Netsolver is for.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What it does</h2>



<h3 class="wp-block-heading">LAN scanning that actually tells you something</h3>



<p class="wp-block-paragraph">When you open Netsolver, it scans your local network and finds every device. But it doesn&#8217;t just list IP addresses — it probes each host for running services. SSH on port 22. SMB on 445. DNS on 53. RDP on 3389. MySQL on 3306.</p>



<p class="wp-block-paragraph">The result is an immediate picture of your network. You can filter hosts by detected service — tap &#8220;SSH&#8221; and see every machine you can log into. Tap &#8220;RDP&#8221; and see your Windows boxes. This is information I used to piece together from nmap output and spreadsheets. Now it&#8217;s on my phone in seconds.</p>



<h3 class="wp-block-heading">SSH with saved commands</h3>



<p class="wp-block-paragraph">Connecting to a host takes one tap. You can run any command and see the output. But where it gets useful is saved commands — per-host shortcuts for the things you run all the time. <code>df -h /</code> to check disk. <code>uptime</code> to see load. <code>systemctl status nginx</code> to check a service.</p>



<p class="wp-block-paragraph">On iPad, saved commands sit in a persistent panel alongside the terminal. On iPhone, they live in a strip above the keyboard. No more retyping the same commands every session.</p>



<h3 class="wp-block-heading">Agent deployment and monitoring</h3>



<p class="wp-block-paragraph">This is where Netsolver goes beyond an SSH client.</p>



<p class="wp-block-paragraph">You can deploy a lightweight monitoring agent to any Linux or Mac host directly from the app. One tap — Netsolver connects via SSH, uploads the agent, and installs it as a system service. The agent phones home and starts reporting CPU, memory, and disk usage.</p>



<p class="wp-block-paragraph">You set the thresholds. CPU above 85%? Disk above 90%? The agent fires a push notification to your iPhone via APNs. No polling, no background refresh, no battery drain. The server tells your phone something is wrong.</p>



<h3 class="wp-block-heading">Alerts that actually do something</h3>



<p class="wp-block-paragraph">Here&#8217;s the part that genuinely surprised me when I built it.</p>



<p class="wp-block-paragraph">iOS Shortcuts integration means an alert isn&#8217;t just a notification — it can trigger an action. When a monitored host hits a CPU threshold, Netsolver can fire a Shortcut. That Shortcut can send you a message on a specific channel, log the event to a spreadsheet, kick off a webhook, or run any automation you&#8217;ve already built on your phone.</p>



<p class="wp-block-paragraph">I&#8217;ve worked with monitoring systems that cost thousands of dollars a month and didn&#8217;t have this kind of flexibility. The fact that it&#8217;s available to a home lab operator for the price of an iOS app still seems remarkable to me.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What I learned building it</h2>



<p class="wp-block-paragraph">This is my first production iOS app after 37 years of infrastructure work. A few things surprised me.</p>



<p class="wp-block-paragraph"><strong>The hard part wasn&#8217;t the network code.</strong> LAN scanning, SSH, SFTP — this is the stuff I know. The hard part was SwiftUI. Specifically, building an adaptive layout that works well on iPhone <em>and</em> iPad <em>and</em> adapts gracefully when the user rotates, changes window size, or uses split view. Apple&#8217;s <code>NavigationSplitView</code> is elegant once you understand it, but it took me a while to understand it.</p>



<p class="wp-block-paragraph"><strong>Privacy-first design is harder than it sounds.</strong> I wanted zero cloud, zero account, credentials in the Keychain only. That&#8217;s the right decision for an app that handles SSH keys. But it means solving problems that cloud sync would solve for free — like keeping inventory in sync across your iPhone and iPad. SwiftData with CloudKit private database turned out to be the right answer, but getting there took research.</p>



<p class="wp-block-paragraph"><strong>The Shortcuts integration was genuinely fun.</strong> I&#8217;ve been writing automation scripts my whole career. Hooking into iOS Shortcuts and watching a server alert trigger a cascade of phone-side automation felt like two worlds I care about finally talking to each other.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Who it&#8217;s for</h2>



<p class="wp-block-paragraph">Netsolver is built for three kinds of people:</p>



<p class="wp-block-paragraph"><strong>Home lab operators</strong> running 3–10 machines. You have a NAS, a Pi or two, maybe a repurposed desktop. You want to know they&#8217;re up, and you want to SSH in without hunting for credentials.</p>



<p class="wp-block-paragraph"><strong>Small business owners</strong> with a handful of servers or sites to watch. You don&#8217;t want to pay enterprise RMM pricing for eight machines. You want a native iOS app that just works.</p>



<p class="wp-block-paragraph"><strong>Freelance developers and web designers</strong> who SSH into client servers regularly. You want saved commands, SFTP access, and a way to know immediately if a client site goes dark.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Coming soon</h2>



<p class="wp-block-paragraph">Netsolver is in final development. The core is working: scanning, SSH, SFTP, agent deployment, APNs alerts, and Shortcuts integration. I&#8217;m working through the UI polish and App Store submission process now.</p>



<p class="wp-block-paragraph">If you run a home lab or manage a small fleet of machines, this is being built for you.</p>



<p class="wp-block-paragraph">I&#8217;ll post an update here when it&#8217;s available on the App Store. In the meantime, you can find my other apps and development work at <a href="https://robroy.online">robroy.online</a>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>Rob Roy is an indie iOS and macOS developer based in Maine with 37 years of software development experience, including DevOps and systems administration roles at IBM and Disney.</em></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2414</post-id>	</item>
		<item>
		<title>Xcode Debugging hint</title>
		<link>https://www.robert-e-roy.com/xcode-debugging-hint/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Sun, 15 Mar 2026 16:26:04 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2412</guid>

					<description><![CDATA[A lightweight, zero-cost debugging utility for Swift projects that automatically strips debug statements from release builds. ## Overview The `DebugHelpers.swift` file provides debug logging macros that are completely removed during release builds, ensuring zero performance impact in production. ## Features &#8211; **Zero runtime cost in release builds** &#8211; All debug code is stripped at compile ... <a title="Xcode Debugging hint" class="read-more" href="https://www.robert-e-roy.com/xcode-debugging-hint/" aria-label="Read more about Xcode Debugging hint">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p data-wp-context---core-fit-text="core/fit-text::{&quot;fontSize&quot;:&quot;&quot;}" data-wp-init---core-fit-text="core/fit-text::callbacks.init" data-wp-interactive data-wp-style--font-size="core/fit-text::context.fontSize" class="has-fit-text wp-block-paragraph">A lightweight, zero-cost debugging utility for Swift projects that automatically strips debug statements from release builds.</p>



<p class="wp-block-paragraph">## Overview</p>



<p class="wp-block-paragraph">The `DebugHelpers.swift` file provides debug logging macros that are completely removed during release builds, ensuring zero performance impact in production.</p>



<p class="wp-block-paragraph">## Features</p>



<p class="wp-block-paragraph">&#8211; **Zero runtime cost in release builds** &#8211; All debug code is stripped at compile time</p>



<p class="wp-block-paragraph">&#8211; **Simple API** &#8211; Drop-in replacement for `print()`</p>



<p class="wp-block-paragraph">&#8211; **File and line tracking** &#8211; Optional detailed logging with source location</p>



<p class="wp-block-paragraph">&#8211; **Debug assertions** &#8211; Catch issues early in development</p>



<p class="wp-block-paragraph">## Usage</p>



<p class="wp-block-paragraph">### Basic Debug Logging</p>



<p class="wp-block-paragraph">Replace `print()` with `DLOG()`:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">// Old way (appears in release builds):</p>



<p class="wp-block-paragraph">print(&#8220;User logged in:&#8221;, username)</p>



<p class="wp-block-paragraph">// New way (stripped from release builds):</p>



<p class="wp-block-paragraph">DLOG(&#8220;User logged in:&#8221;, username)</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### Debug Logging with Source Location</p>



<p class="wp-block-paragraph">When you need to know exactly where a log came from:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">DLOG_TRACE(&#8220;Error occurred&#8221;)</p>



<p class="wp-block-paragraph">// Output: [ViewController.swift:42] Error occurred</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### Debug Assertions</p>



<p class="wp-block-paragraph">Catch programming errors during development:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">DASSERT(user.age &gt; 0, &#8220;User age must be positive&#8221;)</p>



<p class="wp-block-paragraph">// Only fires in DEBUG builds</p>



<p class="wp-block-paragraph">// In release: completely removed</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">## API Reference</p>



<p class="wp-block-paragraph">### `DLOG(_ items: Any&#8230;, separator: String = &#8221; &#8220;, terminator: String = &#8220;\n&#8221;)`</p>



<p class="wp-block-paragraph">Basic debug print that works exactly like Swift&#8217;s `print()` but only in DEBUG builds.</p>



<p class="wp-block-paragraph">**Parameters:**</p>



<p class="wp-block-paragraph">&#8211; `items` &#8211; Values to print</p>



<p class="wp-block-paragraph">&#8211; `separator` &#8211; String to separate values (default: &#8221; &#8220;)</p>



<p class="wp-block-paragraph">&#8211; `terminator` &#8211; String to end the line (default: &#8220;\n&#8221;)</p>



<p class="wp-block-paragraph">**Example:**</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">DLOG(&#8220;Processing&#8221;, itemCount, &#8220;items&#8221;)</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### `DLOG_TRACE(_ items: Any&#8230;, file: String = #file, line: Int = #line)`</p>



<p class="wp-block-paragraph">Debug print with file and line number information.</p>



<p class="wp-block-paragraph">**Parameters:**</p>



<p class="wp-block-paragraph">&#8211; `items` &#8211; Values to print</p>



<p class="wp-block-paragraph">&#8211; `file` &#8211; Source file (automatically captured)</p>



<p class="wp-block-paragraph">&#8211; `line` &#8211; Line number (automatically captured)</p>



<p class="wp-block-paragraph">**Example:**</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">DLOG_TRACE(&#8220;Unexpected state&#8221;)</p>



<p class="wp-block-paragraph">// Output: [MyView.swift:127] Unexpected state</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### `DASSERT(_ condition: Bool, _ message: String = &#8220;&#8221;, file: String = #file, line: Int = #line)`</p>



<p class="wp-block-paragraph">Debug-only assertion that crashes the app if condition is false.</p>



<p class="wp-block-paragraph">**Parameters:**</p>



<p class="wp-block-paragraph">&#8211; `condition` &#8211; Boolean condition to check</p>



<p class="wp-block-paragraph">&#8211; `message` &#8211; Description of the assertion</p>



<p class="wp-block-paragraph">&#8211; `file` &#8211; Source file (automatically captured)</p>



<p class="wp-block-paragraph">&#8211; `line` &#8211; Line number (automatically captured)</p>



<p class="wp-block-paragraph">**Example:**</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">DASSERT(array.count &gt; 0, &#8220;Array should not be empty&#8221;)</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">## How It Works</p>



<p class="wp-block-paragraph">The macros use Swift&#8217;s `#if DEBUG` preprocessor directive:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">func DLOG(_ items: Any&#8230;) {</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; #if DEBUG</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; print(items)</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; #endif</p>



<p class="wp-block-paragraph">}</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">When you build for release (without the DEBUG flag), the compiler completely removes all code inside `#if DEBUG` blocks. This means:</p>



<p class="wp-block-paragraph">&#8211; No runtime overhead</p>



<p class="wp-block-paragraph">&#8211; No string allocations</p>



<p class="wp-block-paragraph">&#8211; No function calls</p>



<p class="wp-block-paragraph">&#8211; Zero bytes in the binary</p>



<p class="wp-block-paragraph">## Setting Up in New Projects</p>



<p class="wp-block-paragraph">### Option 1: File Template</p>



<p class="wp-block-paragraph">1. Create the template directory:</p>



<p class="wp-block-paragraph">&#8220;`bash</p>



<p class="wp-block-paragraph">mkdir -p ~/Library/Developer/Xcode/Templates/File\ Templates/Custom/Debug\ Helper.xctemplate</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">2. Copy the template files to that directory</p>



<p class="wp-block-paragraph">3. Restart Xcode</p>



<p class="wp-block-paragraph">4. New file → Custom → Debug Helper</p>



<p class="wp-block-paragraph">### Option 2: Code Snippet</p>



<p class="wp-block-paragraph">1. In Xcode, select the `DebugHelpers.swift` code</p>



<p class="wp-block-paragraph">2. Right-click → Create Code Snippet</p>



<p class="wp-block-paragraph">3. Set completion shortcut: `debughelpers`</p>



<p class="wp-block-paragraph">4. In any project, type `debughelpers` + Enter</p>



<p class="wp-block-paragraph">### Option 3: Manual Copy</p>



<p class="wp-block-paragraph">Simply drag `DebugHelpers.swift` into your new project.</p>



<p class="wp-block-paragraph">## Best Practices</p>



<p class="wp-block-paragraph">### DO:</p>



<p class="wp-block-paragraph">&#8211; Use `DLOG()` for temporary debugging during development</p>



<p class="wp-block-paragraph">&#8211; Use `DLOG_TRACE()` when you need to track execution flow</p>



<p class="wp-block-paragraph">&#8211; Use `DASSERT()` to catch programming errors early</p>



<p class="wp-block-paragraph">&#8211; Remove debug statements once issues are resolved</p>



<p class="wp-block-paragraph">### DON&#8217;T:</p>



<p class="wp-block-paragraph">&#8211; Don&#8217;t use for production logging (use OSLog or a logging framework)</p>



<p class="wp-block-paragraph">&#8211; Don&#8217;t log sensitive information (even in debug builds)</p>



<p class="wp-block-paragraph">&#8211; Don&#8217;t use as a permanent logging solution</p>



<p class="wp-block-paragraph">## Benefits Over `print()`</p>



<p class="wp-block-paragraph">| Feature | `print()` | `DLOG()` |</p>



<p class="wp-block-paragraph">|&#8212;&#8212;&#8212;|&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;-|</p>



<p class="wp-block-paragraph">| Works in DEBUG | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> |</p>



<p class="wp-block-paragraph">| Works in RELEASE | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (intentional) |</p>



<p class="wp-block-paragraph">| Runtime cost in release | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Yes | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Zero |</p>



<p class="wp-block-paragraph">| Binary size impact | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Increases | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> None |</p>



<p class="wp-block-paragraph">| String evaluation cost | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Always | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> DEBUG only |</p>



<p class="wp-block-paragraph">## Migration Guide</p>



<p class="wp-block-paragraph">### Finding print statements to replace:</p>



<p class="wp-block-paragraph">&#8220;`bash</p>



<p class="wp-block-paragraph"># Find all print() calls in Swift files</p>



<p class="wp-block-paragraph">grep -r &#8220;print(&#8221; &#8211;include=&#8221;*.swift&#8221; .</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### Replace with DLOG:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">// Before:</p>



<p class="wp-block-paragraph">print(&#8220;Loading data&#8230;&#8221;)</p>



<p class="wp-block-paragraph">print(&#8220;Items:&#8221;, items.count)</p>



<p class="wp-block-paragraph">print(&#8220;Error:&#8221;, error.localizedDescription)</p>



<p class="wp-block-paragraph">// After:</p>



<p class="wp-block-paragraph">DLOG(&#8220;Loading data&#8230;&#8221;)</p>



<p class="wp-block-paragraph">DLOG(&#8220;Items:&#8221;, items.count)</p>



<p class="wp-block-paragraph">DLOG(&#8220;Error:&#8221;, error.localizedDescription)</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">## Advanced Usage</p>



<p class="wp-block-paragraph">### Custom Debug Helpers</p>



<p class="wp-block-paragraph">You can extend the pattern for specific needs:</p>



<p class="wp-block-paragraph">&#8220;`swift</p>



<p class="wp-block-paragraph">#if DEBUG</p>



<p class="wp-block-paragraph">func DLOG_NETWORK(_ url: URL, _ response: HTTPURLResponse?) {</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; print(&#8220;<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Network: \(url)&#8221;)</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; if let status = response?.statusCode {</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; &nbsp; &nbsp; print(&#8221; &nbsp; Status: \(status)&#8221;)</p>



<p class="wp-block-paragraph">&nbsp; &nbsp; }</p>



<p class="wp-block-paragraph">}</p>



<p class="wp-block-paragraph">#else</p>



<p class="wp-block-paragraph">func DLOG_NETWORK(_ url: URL, _ response: HTTPURLResponse?) {}</p>



<p class="wp-block-paragraph">#endif</p>



<p class="wp-block-paragraph">&#8220;`</p>



<p class="wp-block-paragraph">### Conditional Compilation Flags</p>



<p class="wp-block-paragraph">Check your build configuration:</p>



<p class="wp-block-paragraph">&#8211; Xcode → Target → Build Settings → Swift Compiler &#8211; Custom Flags</p>



<p class="wp-block-paragraph">&#8211; DEBUG should be in &#8220;Other Swift Flags&#8221; for Debug configuration: `-D DEBUG`</p>



<p class="wp-block-paragraph">## Troubleshooting</p>



<p class="wp-block-paragraph">### &#8220;DLOG is showing in release builds&#8221;</p>



<p class="wp-block-paragraph">Check your build configuration:</p>



<p class="wp-block-paragraph">1. Select your target</p>



<p class="wp-block-paragraph">2. Build Settings → Swift Compiler &#8211; Custom Flags</p>



<p class="wp-block-paragraph">3. Debug configuration should have `-D DEBUG`</p>



<p class="wp-block-paragraph">4. Release configuration should NOT have `-D DEBUG`</p>



<p class="wp-block-paragraph">### &#8220;Cannot find DLOG in scope&#8221;</p>



<p class="wp-block-paragraph">Make sure `DebugHelpers.swift` is included in your target&#8217;s compile sources:</p>



<p class="wp-block-paragraph">1. Select the file in project navigator</p>



<p class="wp-block-paragraph">2. File Inspector → Target Membership</p>



<p class="wp-block-paragraph">3. Check your app target</p>



<p class="wp-block-paragraph">## Performance Notes</p>



<p class="wp-block-paragraph">In DEBUG builds, these macros have the same performance as `print()`.</p>



<p class="wp-block-paragraph">In RELEASE builds:</p>



<p class="wp-block-paragraph">&#8211; **Zero CPU cost** &#8211; Code doesn&#8217;t exist</p>



<p class="wp-block-paragraph">&#8211; **Zero memory cost** &#8211; Strings not allocated</p>



<p class="wp-block-paragraph">&#8211; **Zero binary size** &#8211; Completely stripped</p>



<p class="wp-block-paragraph">Example binary size comparison on a real project:</p>



<p class="wp-block-paragraph">&#8211; With 100 `print()` statements: +8KB</p>



<p class="wp-block-paragraph">&#8211; With 100 `DLOG()` statements: +0KB i</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2412</post-id>	</item>
		<item>
		<title>Introducing Flying Moose Alchemy</title>
		<link>https://www.robert-e-roy.com/introducing-flying-moose-alchemy/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 18:01:15 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2399</guid>

					<description><![CDATA[I&#8217;ve been building websites for over two decades — starting with hand-coded HTML long before WordPress existed, and growing with the web ever since. Alongside my app development work, I take on web design projects for small businesses that need a real web presence without a big agency price tag. Why I Do This Same ... <a title="Introducing Flying Moose Alchemy" class="read-more" href="https://www.robert-e-roy.com/introducing-flying-moose-alchemy/" aria-label="Read more about Introducing Flying Moose Alchemy">Read more</a>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">I&#8217;ve been building websites for over two decades — starting with hand-coded HTML long before WordPress existed, and growing with the web ever since. Alongside my app development work, I take on web design projects for small businesses that need a real web presence without a big agency price tag.</p>



<h2 class="wp-block-heading">Why I Do This</h2>



<p class="wp-block-paragraph">Same reason I build free apps for the recovery community — I like helping people. Small business owners are usually great at what they do and deserve a website that reflects that. What they don&#8217;t always have is the time, budget, or technical background to make it happen. That&#8217;s where I come in.</p>



<p class="wp-block-paragraph">A recent example is <a href="https://flyingmoosealchemy.com" target="_blank" rel="noreferrer noopener">Flying Moose Alchemy</a>, a unique art and craft business I built a site for. Every business has its own story — the job is making sure the website tells it.</p>



<h2 class="wp-block-heading">WordPress, So You Stay Independent</h2>



<p class="wp-block-paragraph">Every site I build is on WordPress, and that&#8217;s a deliberate choice. A lot of web designers hand you a finished site and then you&#8217;re dependent on them for every small change. I don&#8217;t work that way. WordPress means you can log in and update your own content — add a new product, change your hours, post an announcement — without calling anyone or paying for a simple edit. You own your site and you can run it yourself.</p>



<p class="wp-block-paragraph">Twenty years of working with WordPress also means I know it well enough to build something clean and fast, not just something that technically works.</p>



<h2 class="wp-block-heading">For Businesses That Are a Little Different</h2>



<p class="wp-block-paragraph">I&#8217;m particularly drawn to unique small businesses — the ones that don&#8217;t fit a template. Cookie-cutter sites work fine for cookie-cutter businesses. If what you do is distinctive, your website should reflect that. I take the time to understand what makes your business worth talking about and build something that actually communicates it.</p>



<h2 class="wp-block-heading">Affordable by Design</h2>



<p class="wp-block-paragraph">I&#8217;m a solo developer working lean, which means my overhead is low and I can offer real value at a price that makes sense for a small business. You&#8217;re not paying for a sales team, account managers, or a fancy office. You&#8217;re paying for two decades of experience and someone who actually cares whether your site works for you.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">If you&#8217;re a small business owner thinking about a new site or a redesign, get in touch at <a href="mailto:contact@robroy.online">contact@robroy.online</a>. I&#8217;m happy to talk through what you need before you commit to anything.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2399</post-id>	</item>
		<item>
		<title>Introducing 12and12 — A Free Widget App for Anyone in Recovery</title>
		<link>https://www.robert-e-roy.com/introducing-12and12-a-free-widget-app-for-anyone-in-recovery/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 17:41:00 +0000</pubDate>
				<category><![CDATA[iOS apps]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2396</guid>

					<description><![CDATA[Not every app I build is about solving a technical problem. Sometimes it&#8217;s about giving something back. 12and12 is a free iOS app that puts daily recovery readings right on your home screen and lock screen as widgets. No subscriptions, no ads, no catch. I built it because the recovery community has given a lot ... <a title="Introducing 12and12 — A Free Widget App for Anyone in Recovery" class="read-more" href="https://www.robert-e-roy.com/introducing-12and12-a-free-widget-app-for-anyone-in-recovery/" aria-label="Read more about Introducing 12and12 — A Free Widget App for Anyone in Recovery">Read more</a>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">Not every app I build is about solving a technical problem. Sometimes it&#8217;s about giving something back.</p>



<p class="wp-block-paragraph">12and12 is a free iOS app that puts daily recovery readings right on your home screen and lock screen as widgets. No subscriptions, no ads, no catch. I built it because the recovery community has given a lot of people — including people I care about — something invaluable, and this was a way I could contribute something back using the skills I have.</p>



<h2 class="wp-block-heading">What It Does</h2>



<p class="wp-block-paragraph">The app provides home screen and lock screen widgets with daily readings drawn from the 12 Steps and 12 Traditions. The idea is simple: keep your recovery front and center throughout the day, right where you&#8217;re already looking. You don&#8217;t have to open an app, navigate a menu, or remember to check in. The reading is just there.</p>



<h2 class="wp-block-heading">Why Widgets</h2>



<p class="wp-block-paragraph">A lot of recovery apps ask you to open them daily, which adds friction. Widgets work differently — they&#8217;re part of your phone&#8217;s background, something you see naturally as you go about your day. A brief reminder on your lock screen when you pick up your phone in the morning costs nothing and takes no effort. That low friction felt right for something meant to support a daily practice.</p>



<h2 class="wp-block-heading">It&#8217;s Completely Free</h2>



<p class="wp-block-paragraph">There&#8217;s no paid version, no premium tier, no in-app purchases. 12and12 is free because that&#8217;s the point. Recovery communities are built on people helping people without expectation of anything in return. This app is my version of that.</p>



<h2 class="wp-block-heading">How to Get It</h2>



<p class="wp-block-paragraph">12and12 is available now on the App Store for iPhone. After you download it, add a widget to your home screen or lock screen through the standard iOS widget menu — press and hold your home screen, tap the + button, and search for 12and12.</p>



<p class="wp-block-paragraph"><a href="https://apps.apple.com/app/12-and-12/id6758464299">Download 12and12 on the App Store</a></p>



<p class="wp-block-paragraph">If it&#8217;s useful to you or someone you know, I&#8217;m glad. That&#8217;s enough.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2396</post-id>	</item>
		<item>
		<title>I Used Claude Code to Build SmartFiler — Here&#8217;s the Honest Truth</title>
		<link>https://www.robert-e-roy.com/i-used-claude-code-to-build-smartfiler-heres-the-honest-truth/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 17:31:05 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2389</guid>

					<description><![CDATA[When I started building SmartFiler, I decided to use Claude Code throughout the development process. Not as a curiosity, but as a real part of my workflow. I&#8217;m an indie developer — solo, no team — so anything that moves the needle on speed matters. After shipping the app, here&#8217;s what I actually found. What ... <a title="I Used Claude Code to Build SmartFiler — Here&#8217;s the Honest Truth" class="read-more" href="https://www.robert-e-roy.com/i-used-claude-code-to-build-smartfiler-heres-the-honest-truth/" aria-label="Read more about I Used Claude Code to Build SmartFiler — Here&#8217;s the Honest Truth">Read more</a>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">When I started building SmartFiler, I decided to use Claude Code throughout the development process. Not as a curiosity, but as a real part of my workflow. I&#8217;m an indie developer — solo, no team — so anything that moves the needle on speed matters. After shipping the app, here&#8217;s what I actually found.</p>



<h2 class="wp-block-heading">What Claude Code Is (Quick Version)</h2>



<p class="wp-block-paragraph">Claude Code is an AI coding assistant you run from the command line. Unlike a chatbot you paste code into, it operates directly in your project — reading files, writing code, running commands. It&#8217;s aware of your actual codebase, not just whatever snippet you hand it.</p>



<h2 class="wp-block-heading">Where It Really Helped</h2>



<h3 class="wp-block-heading">Getting a Prototype Running Fast</h3>



<p class="wp-block-paragraph">This is where Claude Code genuinely impressed me. I could describe what I wanted — a file organizer with rule-based sorting, duplicate detection, batch renaming — and get a working prototype up and running much faster than I would have alone. Not perfect code, but running code. For a solo developer trying to validate an idea quickly, that&#8217;s valuable. The early momentum it created was real.</p>



<h3 class="wp-block-heading">Algorithms</h3>



<p class="wp-block-paragraph">Some of the more specific algorithms in SmartFiler — the SHA-256 duplicate detection, the fuzzy filename matching — Claude Code handled well. Give it a clear problem with defined inputs and outputs and it produces solid, correct implementations. I didn&#8217;t have to spend hours digging through documentation or working out the logic from scratch. Describe the problem clearly, review the output carefully, and you&#8217;re in good shape.</p>



<h2 class="wp-block-heading">Where It Fell Short</h2>



<h3 class="wp-block-heading">Architecture — Don&#8217;t Trust It Here</h3>



<p class="wp-block-paragraph">This is the big one. Left to its own devices, Claude Code does not naturally write modular, maintainable code. It tends to produce code that works but is tangled — logic that belongs in separate modules ends up in one place, responsibilities bleed across files, things get hard to follow fast. On a small prototype that doesn&#8217;t matter much. On a real app you&#8217;re going to maintain and extend, it matters a lot.</p>



<p class="wp-block-paragraph">My fix: I explicitly prompted it to rewrite sections in the most modular form possible. That instruction has to come from you — it won&#8217;t volunteer it. Once I started treating &#8220;make this more modular&#8221; as a standard part of my workflow, the output got significantly better. But you have to know to ask.</p>



<h3 class="wp-block-heading">It Needs Guidance, Not Just Requests</h3>



<p class="wp-block-paragraph">The mental model that helped me most: Claude Code is a skilled contractor, not a senior architect. If you hand a good contractor a clear spec, they&#8217;ll execute it well. If you ask them to figure out the structure of the whole project, you&#8217;ll get something that technically works but probably isn&#8217;t how you&#8217;d have done it yourself. The guidance still has to come from you.</p>



<h2 class="wp-block-heading">The Most Valuable Use I Found</h2>



<p class="wp-block-paragraph">Code review — but not the way you might expect. I reviewed the code myself, identified what needed improving, and then directed Claude Code to make specific changes based on my assessment. As a solo developer you don&#8217;t have a teammate to bounce ideas off, so having something that can execute your improvement suggestions quickly is genuinely useful. The critical thinking still came from me. Claude Code handled the implementation.</p>



<p class="wp-block-paragraph">My workflow became: write or generate code → review it myself → direct Claude Code on what to fix or improve → repeat. That loop worked well, and it kept me in control of where the codebase was heading.</p>



<h2 class="wp-block-heading">My Honest Take for Indie Developers</h2>



<p class="wp-block-paragraph">Claude Code is a real productivity tool, not just hype — but it&#8217;s not a shortcut to skip thinking about your software. The developers who will get the most out of it are the ones who already know what good code looks like and can evaluate what it produces. If you&#8217;re experienced enough to recognize when it&#8217;s going sideways, it&#8217;ll speed you up considerably. If you&#8217;re not yet at that point, it could just as easily lead you in a bad direction faster.</p>



<p class="wp-block-paragraph">For SmartFiler, it accelerated the early stages significantly and helped me punch above my weight as a solo dev. I&#8217;ll use it on my next project. But I&#8217;ll be the one making the architectural decisions.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">SmartFiler is currently under App Store review. You can read more about it on the <a href="https://robroy.online/smartfileorganizer/">product page</a>. If you&#8217;re an indie developer using AI tools in your workflow, I&#8217;d be curious what you&#8217;re finding — drop me a line at <a href="mailto:contact@robroy.online">contact@robroy.online</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2389</post-id>	</item>
		<item>
		<title>Introducing SmartFiler — A Mac File Organizer I Actually Wanted to Use</title>
		<link>https://www.robert-e-roy.com/introducing-smartfiler-a-mac-file-organizer-i-actually-wanted-to-use/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 17:21:46 +0000</pubDate>
				<category><![CDATA[Mac OS]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2387</guid>

					<description><![CDATA[I just submitted SmartFiler to the Mac App Store, and I&#8217;m pretty excited about this one. It&#8217;s a file organization tool for macOS that I built because every alternative I tried was either too complicated, too aggressive with your files, or just didn&#8217;t fit the way I actually work. Here&#8217;s what it does. Find Duplicates ... <a title="Introducing SmartFiler — A Mac File Organizer I Actually Wanted to Use" class="read-more" href="https://www.robert-e-roy.com/introducing-smartfiler-a-mac-file-organizer-i-actually-wanted-to-use/" aria-label="Read more about Introducing SmartFiler — A Mac File Organizer I Actually Wanted to Use">Read more</a>]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">I just submitted <strong>SmartFiler</strong> to the Mac App Store, and I&#8217;m pretty excited about this one. It&#8217;s a file organization tool for macOS that I built because every alternative I tried was either too complicated, too aggressive with your files, or just didn&#8217;t fit the way I actually work.</p>



<p class="wp-block-paragraph">Here&#8217;s what it does.</p>



<h2 class="wp-block-heading">Find Duplicates — by Name or by Content</h2>



<p class="wp-block-paragraph">SmartFiler can scan a folder and find duplicate files two ways: matching filenames, or matching actual file content using SHA-256 hashing. The content match is the one you want — it catches files that got renamed but are still identical copies taking up space. You review everything before anything gets deleted, and when you do delete, it goes to Trash, not gone forever.</p>



<h2 class="wp-block-heading">Smart Buckets</h2>



<p class="wp-block-paragraph">This is my favorite feature. You define rules — by filename, path, or extension — and SmartFiler sorts your files into named folders automatically. Set up a bucket for invoices, one for screenshots, one for project files. You drag to reorder priority, toggle buckets on and off, and nothing moves until you say go.</p>



<h2 class="wp-block-heading">Similar Files</h2>



<p class="wp-block-paragraph">Fuzzy filename matching that surfaces files that are probably related — like <code>report_2025.pdf</code> and <code>report_2026.pdf</code>. You control how loose or strict the matching is with a slider. Useful for cleaning up versioned files that accumulated over time.</p>



<h2 class="wp-block-heading">Auto-Organize by Pattern</h2>



<p class="wp-block-paragraph">Point SmartFiler at a messy folder and it analyzes the files and suggests groupings — by file type, date, numbered sequences, or common name prefixes. You review the suggested groups, check or uncheck what you want, preview the changes, then organize. No surprises.</p>



<h2 class="wp-block-heading">Filename Cleaner</h2>



<p class="wp-block-paragraph">Batch rename files to strip out special characters, fix spacing, change case, or apply a regex pattern. If you&#8217;ve ever downloaded a folder of files with names like <code>IMG_20240315_094512_FINAL_v2 (1).jpg</code>, you know why this exists.</p>



<h2 class="wp-block-heading">Built Around Preview First</h2>



<p class="wp-block-paragraph">Every operation in SmartFiler shows you what&#8217;s going to happen before it happens. You can cancel long-running scans at any time. Deletions go to Trash. I built it this way on purpose — file operations are the kind of thing where you really don&#8217;t want surprises.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">SmartFiler is currently under review at the Mac App Store. I&#8217;ll post the link here as soon as it&#8217;s live. In the meantime, you can check out the <a href="https://robroy.online/smartfileorganizer/">product page</a> for more details.</p>



<p class="wp-block-paragraph">If you&#8217;ve got feedback or questions, drop me a line at <a href="mailto:contact@robroy.online">contact@robroy.online</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2387</post-id>	</item>
		<item>
		<title>Evidr</title>
		<link>https://www.robert-e-roy.com/evidr/</link>
		
		<dc:creator><![CDATA[robroy]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 20:24:43 +0000</pubDate>
				<category><![CDATA[iOS apps]]></category>
		<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://www.robert-e-roy.com/?p=2377</guid>

					<description><![CDATA[Document everything. Prove anything. For Homeowners When dealing with property damage, theft, vandalism, or disputes, keep all your evidence organized and ready to share. For Developers Track bugs with structured reports — severity, steps to reproduce, expected vs. actual behavior, and environment details.]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-pullquote"><blockquote><p><a href="https://www.robroy.online/evidr/" data-type="link" data-id="https://www.robroy.online/evidr/">Document</a> everything. Prove anything.</p></blockquote></figure>



<h3 class="wp-block-heading">For Homeowners</h3>



<p class="wp-block-paragraph">When dealing with property damage, theft, vandalism, or disputes, keep all your evidence organized and ready to share.</p>



<ul class="wp-block-list">
<li>Security camera footage</li>



<li>Photos &amp; documents</li>



<li>Insurance claim reports</li>



<li>Timestamped notes</li>



<li>Professional PDF exports</li>
</ul>



<h3 class="wp-block-heading">For Developers</h3>



<p class="wp-block-paragraph">Track bugs with structured reports — severity, steps to reproduce, expected vs. actual behavior, and environment details.</p>



<ul class="wp-block-list">
<li>Bug ID &amp; severity tracking</li>



<li>Steps to reproduce</li>



<li>Expected vs. actual behavior</li>



<li>Environment details</li>



<li>Screenshot attachments</li>
</ul>



<p class="wp-block-paragraph"></p>



<figure class="wp-block-image size-large"><a href="https://apps.apple.com/app/evidr/id6759003012" target="_blank" rel=" noreferrer noopener"><img decoding="async" src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-us?size=250x83" alt=""/></a></figure>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2377</post-id>	</item>
	</channel>
</rss>
