<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ahab</title>
    <description>The latest articles on DEV Community by Ahab (@ahab_indieseek).</description>
    <link>https://dev.to/ahab_indieseek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4014445%2F97c86b36-e09c-4828-816c-c828c0c15b0e.jpg</url>
      <title>DEV Community: Ahab</title>
      <link>https://dev.to/ahab_indieseek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahab_indieseek"/>
    <language>en</language>
    <item>
      <title>Why my Product Hunt launch failed: 10 votes, 1 download, and lessons from launching Paste Switch</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:28:39 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/why-my-product-hunt-launch-failed-10-votes-1-download-and-lessons-from-launching-paste-switch-31bc</link>
      <guid>https://dev.to/ahab_indieseek/why-my-product-hunt-launch-failed-10-votes-1-download-and-lessons-from-launching-paste-switch-31bc</guid>
      <description>&lt;p&gt;On Saturday, July 4, 2026, I launched &lt;a href="https://indieseek.co/apps/paste-switch/" rel="noopener noreferrer"&gt;Paste Switch&lt;/a&gt; on Product Hunt.&lt;/p&gt;

&lt;p&gt;This post is not a victory thread. It is a real build-in-public record of a failed Product Hunt launch: why I built the product, why I moved the launch date from Tuesday to July 4, what happened in the first four hours, why I refused vote-buying, and why I now think launch day matters far less than long-term distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick snapshot
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://indieseek.co/apps/paste-switch/" rel="noopener noreferrer"&gt;Paste Switch&lt;/a&gt;, a macOS utility for shortcut-first clipboard switching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original launch plan&lt;/td&gt;
&lt;td&gt;Tuesday, July 7, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual launch date&lt;/td&gt;
&lt;td&gt;Saturday, July 4, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four-hour public result&lt;/td&gt;
&lt;td&gt;4 votes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current vote count&lt;/td&gt;
&lt;td&gt;10 votes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current Product Hunt exposure&lt;/td&gt;
&lt;td&gt;Close to 100 page impressions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current conversion&lt;/td&gt;
&lt;td&gt;1 download&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main takeaway&lt;/td&gt;
&lt;td&gt;Product Hunt launch day is small. Distribution is the real system.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The product started from a small but repeated frustration
&lt;/h2&gt;

&lt;p&gt;Paste Switch came from a very ordinary workflow problem.&lt;/p&gt;

&lt;p&gt;When I work on a Mac, I often copy several things in a short burst: a URL, a shell command, a sentence, a code snippet, an image, then another line. Traditional clipboard managers can store all of that, but the usual interaction still breaks focus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open history
-&amp;gt; scan the list
-&amp;gt; click the right item
-&amp;gt; paste
-&amp;gt; realize it was the wrong one
-&amp;gt; go back again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wanted a much narrower product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;copy a few things
-&amp;gt; press one shortcut
-&amp;gt; paste the latest item
-&amp;gt; press the same shortcut again
-&amp;gt; replace it in place with the previous item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the core idea behind Paste Switch. It is not trying to become another full clipboard manager. Its main job is to protect attention. You keep typing in the current app, and if the latest paste is wrong, you switch in place instead of stopping to manage history.&lt;/p&gt;

&lt;p&gt;That shortcut-first, switch-in-place interaction is still the biggest difference and the biggest advantage of the product. The panel exists, but it is secondary. The real value is staying in flow.&lt;/p&gt;

&lt;p&gt;I wrote more about the product boundary in &lt;a href="https://indieseek.co/blogs/paste-switch-macos-app-development/" rel="noopener noreferrer"&gt;Building Paste Switch from a small clipboard itch to a focused Mac app&lt;/a&gt;, and more about one of the hardest interaction details in &lt;a href="https://indieseek.co/blogs/macos-input-caret-position-ai-agent/" rel="noopener noreferrer"&gt;How to locate the input position on macOS, and what it took to get it right&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I moved the Product Hunt launch from Tuesday to Saturday
&lt;/h2&gt;

&lt;p&gt;My original plan was to launch on Tuesday, July 7, 2026.&lt;/p&gt;

&lt;p&gt;I changed it to Saturday, July 4, 2026 for a simple reason: I thought it might give the product a better shot at ranking, and I did not have any more meaningful launch preparation left to do.&lt;/p&gt;

&lt;p&gt;The product page was ready. The positioning was clear. The demo video was already on the page. The Product Hunt surface was in place. At that point, waiting a few more days no longer felt like better preparation. It felt like delay.&lt;/p&gt;

&lt;p&gt;So I moved earlier and shipped.&lt;/p&gt;

&lt;p&gt;In hindsight, that decision was still fine. I do not think waiting until Tuesday would have magically fixed the outcome. What launch day exposed was not a scheduling problem. It exposed a distribution problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did right after the Product Hunt launch
&lt;/h2&gt;

&lt;p&gt;After the Product Hunt launch went live, I did what most indie makers do on launch day: I tried to give it as much real visibility as I could.&lt;/p&gt;

&lt;p&gt;I actively shared the update across different platforms and tried to pull more people into the page, the discussion, and the product itself. The goal was straightforward: more people see it, more people click in, and more people interact.&lt;/p&gt;

&lt;p&gt;That part was emotionally easy in the first hour. Launch day has momentum. You have a live page, a public artifact, and a reason to talk about what you built.&lt;/p&gt;

&lt;p&gt;The harder part came later, when the scoreboard stopped matching the effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four hours later, the Product Hunt launch only had four votes
&lt;/h2&gt;

&lt;p&gt;Four hours after launch, the ranking and vote count were basically clear enough to read.&lt;/p&gt;

&lt;p&gt;Paste Switch had only four votes at that point.&lt;/p&gt;

&lt;p&gt;I was disappointed. There is no better way to say it.&lt;/p&gt;

&lt;p&gt;Not because I believed Product Hunt should guarantee distribution. It should not. And not because I thought this one launch should define the product. It should not do that either.&lt;/p&gt;

&lt;p&gt;I was disappointed because the visible output was so much smaller than the work that went into the product, the launch page, the outreach, and the launch-day push. Four hours is not the whole story, but it is long enough to feel the gap between expectation and reality.&lt;/p&gt;

&lt;p&gt;Later, the count climbed to 10 votes. That does not change the core lesson. The launch was still weak, and the first four hours were enough to show that a decent Product Hunt page does not create exposure by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vote-buying offers arrived fast, and I ignored them
&lt;/h2&gt;

&lt;p&gt;After launch, I noticed something else on LinkedIn.&lt;/p&gt;

&lt;p&gt;Several people reached out to sell votes.&lt;/p&gt;

&lt;p&gt;The pitch was roughly the same each time: they could help me add around 100 votes, and the pricing was about $1 per vote.&lt;/p&gt;

&lt;p&gt;I was never going to do that.&lt;/p&gt;

&lt;p&gt;I do not want to build around fake signals, and I do not want to break Product Hunt rules just to make one screenshot look better for a day. If I cannot accept the real result, then I am not actually doing build in public. I am doing theater.&lt;/p&gt;

&lt;p&gt;That does not mean the temptation is not visible. It is very visible. Product Hunt makes ranking public, and the incentive to manipulate a public ranking is obvious. But obvious does not mean acceptable.&lt;/p&gt;

&lt;p&gt;So I left it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leaderboard gap was hard to ignore
&lt;/h2&gt;

&lt;p&gt;The part that really caught my attention was not just my own low vote count. It was the shape of the leaderboard.&lt;/p&gt;

&lt;p&gt;When I checked the results after the first four hours, the top five products all had roughly 110 votes or more. The product in sixth place was only in the teens.&lt;/p&gt;

&lt;p&gt;That gap was huge.&lt;/p&gt;

&lt;p&gt;I cannot prove what happened behind the scenes, and I do not want to present a suspicion as a fact. But the distribution was hard for me to read as purely organic Saturday traffic. If the day had strong natural voting demand across the board, I would expect a smoother curve. Instead, it looked like one cluster at 110-plus and then a steep drop to the rest of the field.&lt;/p&gt;

&lt;p&gt;My personal read is simple: the top of the board likely benefited from growth tactics I was not willing to use.&lt;/p&gt;

&lt;p&gt;That observation does not change my decision. It clarifies it. If a ranking surface rewards tactics I do not want to depend on, then my real moat cannot be that surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger problem was not the votes. It was the conversion.
&lt;/h2&gt;

&lt;p&gt;As of now, the Paste Switch Product Hunt page has close to 100 impressions, but only one of those visits turned into a download.&lt;/p&gt;

&lt;p&gt;That number is much more important than the four votes.&lt;/p&gt;

&lt;p&gt;Ten votes tells me the Product Hunt launch stayed weak. One download tells me the funnel itself is still weak.&lt;/p&gt;

&lt;p&gt;That is the more uncomfortable truth, and also the more useful one.&lt;/p&gt;

&lt;p&gt;If the product page is getting exposure but almost nobody is moving from attention to install, then I need to keep improving at least one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audience quality;&lt;/li&gt;
&lt;li&gt;page-message fit;&lt;/li&gt;
&lt;li&gt;product clarity;&lt;/li&gt;
&lt;li&gt;distribution channel fit;&lt;/li&gt;
&lt;li&gt;or the post-click conversion path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Launch day alone is not the right place to solve that. The correct response is not to rely more on launch day. The correct response is to build a long-term distribution system that keeps compounding after launch day is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am doing after this failed Product Hunt launch
&lt;/h2&gt;

&lt;p&gt;For now, I am not going to rush into building more products just to repeat the same launch pattern.&lt;/p&gt;

&lt;p&gt;The next priority is building my own growth system.&lt;/p&gt;

&lt;p&gt;I am going to focus on three long-term channels.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Share useful software and indie-building ideas on social platforms
&lt;/h3&gt;

&lt;p&gt;I want to keep posting thoughts, observations, and practical notes that are genuinely useful to people building software.&lt;/p&gt;

&lt;p&gt;Not empty promotion. Not just “look what I launched.” Actual value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product lessons;&lt;/li&gt;
&lt;li&gt;distribution lessons;&lt;/li&gt;
&lt;li&gt;software workflow observations;&lt;/li&gt;
&lt;li&gt;and honest build-in-public notes from real experiments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Experiment with AI video workflows on TikTok and YouTube
&lt;/h3&gt;

&lt;p&gt;The second channel is video.&lt;/p&gt;

&lt;p&gt;I want to test interesting AI video formats and also explore what an automated or semi-automated video workflow could look like in practice. That is not only a promotion tactic. It is also part of the product and systems work I care about.&lt;/p&gt;

&lt;p&gt;The channel itself is still experimental, but I think it has more upside than treating Product Hunt as the center of everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep improving IndieSeek through SEO, blogs, and free tools
&lt;/h3&gt;

&lt;p&gt;The third channel is SEO, but not in the empty “publish for keywords” sense.&lt;/p&gt;

&lt;p&gt;What I want to keep doing on &lt;a href="https://indieseek.co/" rel="noopener noreferrer"&gt;IndieSeek&lt;/a&gt; is publishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blog posts that solve real questions;&lt;/li&gt;
&lt;li&gt;free tools that are genuinely useful;&lt;/li&gt;
&lt;li&gt;and pages that can keep bringing in relevant traffic over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is slower than launch-day hype, but it is also much more durable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson from this failed Product Hunt launch
&lt;/h2&gt;

&lt;p&gt;Paste Switch is still a product I believe in.&lt;/p&gt;

&lt;p&gt;I still think the core interaction is strong: paste the latest clip, press again if it is wrong, and keep switching in place without opening a history picker. That is a small behavior change, but it protects attention in a way most clipboard tools do not.&lt;/p&gt;

&lt;p&gt;What this launch changed was not my view of the product. It changed my view of where growth actually comes from.&lt;/p&gt;

&lt;p&gt;Product Hunt launch day can create a spike. It cannot replace a system.&lt;/p&gt;

&lt;p&gt;If I want better outcomes, I need a machine that keeps creating attention, trust, and qualified traffic after the launch badge is gone. That means social distribution, video experiments, and steady SEO assets. Not one magic day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;p&gt;This post is mainly a record of what happened with Paste Switch on Product Hunt.&lt;/p&gt;

&lt;p&gt;If you want to follow more of my indie developer journey, product experiments, and lessons from building in public, you can follow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/" rel="noopener noreferrer"&gt;IndieSeek&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/indieseek_co" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/channel/UCzXqfLGzrOCuByCBvFAUkVw" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will keep building, keep sharing, and keep improving the growth system behind the products.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>indie</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>Slack OG image not showing: a debugging checklist for indie sites</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:28:32 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/slack-og-image-not-showing-a-debugging-checklist-for-indie-sites-46fn</link>
      <guid>https://dev.to/ahab_indieseek/slack-og-image-not-showing-a-debugging-checklist-for-indie-sites-46fn</guid>
      <description>&lt;h2&gt;
  
  
  Quick answer
&lt;/h2&gt;

&lt;p&gt;If your page looks correct in a browser but Slack shows no preview image, the problem is usually not "Slack is random." It is usually one of four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the page HTML does not expose a stable &lt;code&gt;og:image&lt;/code&gt; URL;&lt;/li&gt;
&lt;li&gt;Slackbot cannot fetch the image publicly;&lt;/li&gt;
&lt;li&gt;the image route returns headers, redirects, or content Slack does not handle well;&lt;/li&gt;
&lt;li&gt;Slack is showing a cached result from an earlier fetch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most indie sites, the fastest fix is to debug in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;confirm og tags in raw HTML
-&amp;gt; fetch the image URL directly
-&amp;gt; check status, content-type, size, and redirects
-&amp;gt; make sure the image is public and stable
-&amp;gt; reshare after Slack cache time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;This guide is for solo founders, indie hackers, and small product teams who ship marketing pages, blog posts, tool pages, or Product Hunt assets without a big growth team behind them. If you are using static pages, framework-generated metadata, or a dynamic OG image endpoint, this is the failure mode that wastes half a day: the preview looks fine in one place, but Slack still shows a blank card or the wrong image.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed and why this matters now
&lt;/h2&gt;

&lt;p&gt;A July 3, 2026 Show HN post about Wax Spinner described a familiar launch problem: the site worked, but social preview behavior across crawlers was inconsistent, and Slack was one of the hardest platforms to satisfy. Slack's own docs explain the stable part of the story: Slack unfurls links by fetching them with Slackbot, not with the viewer's browser session, and it caches unfurl results for around 30 minutes. The practical lesson is simple: treat Slack preview debugging as a fetch-and-delivery problem, not a design problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Check the raw HTML first
&lt;/h3&gt;

&lt;p&gt;Open the page source or fetch it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/post
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://example.com/post | rg &lt;span class="s1"&gt;'og:image|og:title|og:description'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want a full absolute &lt;code&gt;og:image&lt;/code&gt; URL in the HTML that Slackbot can read without running app code in a browser tab.&lt;/p&gt;

&lt;p&gt;If your page depends on client-side rendering before the OG tags appear, fix that first.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fetch the image URL directly
&lt;/h3&gt;

&lt;p&gt;Take the exact &lt;code&gt;og:image&lt;/code&gt; URL and test it as a standalone asset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com/og-image.jpg
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://example.com/og-image.jpg &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/og-test
file /tmp/og-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check these basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status is &lt;code&gt;200 OK&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;final URL stays on HTTPS;&lt;/li&gt;
&lt;li&gt;content type is a normal image type;&lt;/li&gt;
&lt;li&gt;the file is not blocked behind cookies, auth, or signed sessions;&lt;/li&gt;
&lt;li&gt;the route does not bounce through unnecessary redirects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the image itself is unreliable, the preview will be unreliable too.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Check route stability and platform fetch assumptions
&lt;/h3&gt;

&lt;p&gt;Slack's docs tell you the fetch happens from Slack's side. The HN Wax Spinner debugging story adds a useful field pattern: dynamic image routes can fail for reasons that are not obvious in a browser, especially when global middleware, CDN rules, or security headers are attached to the image response.&lt;/p&gt;

&lt;p&gt;Treat these as review items:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does the image live at a clean, durable URL;&lt;/li&gt;
&lt;li&gt;is the route public without a user session;&lt;/li&gt;
&lt;li&gt;do redirects preserve the final image correctly;&lt;/li&gt;
&lt;li&gt;are global response rules accidentally applied to image endpoints;&lt;/li&gt;
&lt;li&gt;does the route behave differently for bots than for browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dynamic OG images are fine, but they need to behave like stable public files.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Check image quality, not just image existence
&lt;/h3&gt;

&lt;p&gt;Google's image guidance and Discover guidance are not Slack-specific, but they are still useful guardrails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use a large, high-quality image;&lt;/li&gt;
&lt;li&gt;keep the image crawlable;&lt;/li&gt;
&lt;li&gt;make sure the image matches the page's main topic;&lt;/li&gt;
&lt;li&gt;avoid tiny logos as the only preview asset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Re-test with IndieSeek's own tools
&lt;/h3&gt;

&lt;p&gt;Before you blame Slack, make sure the page metadata is correct in the first place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://indieseek.co/tools/og-image-checker/" rel="noopener noreferrer"&gt;OG Image Checker&lt;/a&gt; to inspect the image URL and preview assumptions.&lt;/li&gt;
&lt;li&gt;Use &lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt; to verify title, description, canonical, and OG tags together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the two fastest internal links for this problem because the issue is often half metadata, half delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  A compact decision tree
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is the wrong image showing everywhere?
  Yes -&amp;gt; fix the page metadata first.
  No -&amp;gt; continue.

Is Slack showing no image at all?
  Yes -&amp;gt; fetch the exact og:image URL directly.
  No -&amp;gt; continue.

Does the image URL return 200, public access, and a normal image type?
  No -&amp;gt; fix the asset route.
  Yes -&amp;gt; continue.

Does the route depend on auth, signed params, or fragile redirects?
  Yes -&amp;gt; make it stable and public.
  No -&amp;gt; continue.

Did you just change the image?
  Yes -&amp;gt; wait for Slack cache to refresh, then test again.
  No -&amp;gt; inspect headers and middleware on the image route.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;The first mistake is validating only in a browser. Slack fetches from Slack's infrastructure, so "works on my laptop" is weak evidence.&lt;/p&gt;

&lt;p&gt;The second mistake is using relative or late-injected OG tags. The crawler needs stable HTML and stable absolute URLs.&lt;/p&gt;

&lt;p&gt;The third mistake is serving the image through a route that behaves like an app endpoint instead of a public asset.&lt;/p&gt;

&lt;p&gt;The fourth mistake is changing the image and immediately resharing the same link without accounting for Slack's documented cache window.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Slack use my browser session to render previews?
&lt;/h3&gt;

&lt;p&gt;No. Slack's docs describe server-side fetching and unfurl behavior from Slackbot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a static image file, or can I use a dynamic OG endpoint?
&lt;/h3&gt;

&lt;p&gt;You can use a dynamic route, but it has to behave like a stable public file: normal status code, public access, predictable redirects, and an image response Slack can fetch cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I care about Google image guidance for this?
&lt;/h3&gt;

&lt;p&gt;Yes, as a supporting rule set. Google and Slack are different systems, but large, crawlable, relevant images are a good baseline for both search and sharing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Slack link unfurling docs: &lt;a href="https://api.slack.com/reference/messaging/link-unfurling" rel="noopener noreferrer"&gt;https://api.slack.com/reference/messaging/link-unfurling&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack robots and crawler user agents: &lt;a href="https://api.slack.com/robots" rel="noopener noreferrer"&gt;https://api.slack.com/robots&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google image SEO best practices: &lt;a href="https://developers.google.com/search/docs/appearance/google-images" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/appearance/google-images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Discover image guidance: &lt;a href="https://developers.google.com/search/docs/appearance/google-discover" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/appearance/google-discover&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Article structured data reference: &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/article" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/appearance/structured-data/article&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/og-image-checker/" rel="noopener noreferrer"&gt;OG Image Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Show HN: Wax Spinner, a "now playing" social app for vinyl record collectors (2026-07-03): &lt;a href="https://news.ycombinator.com/item?id=48774122" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=48774122&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>debugging</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Markdown HTML reader for Mac: how to review AI-generated docs</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:23:16 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/markdown-html-reader-for-mac-how-to-review-ai-generated-docs-fna</link>
      <guid>https://dev.to/ahab_indieseek/markdown-html-reader-for-mac-how-to-review-ai-generated-docs-fna</guid>
      <description>&lt;p&gt;AI coding tools do more than change code now. They also leave behind plans, reports, release notes, handoff docs, Mermaid diagrams, and HTML previews.&lt;/p&gt;

&lt;p&gt;Those files are useful, but they usually land inside real project folders. That means they sit next to source code, configs, dependencies, build output, screenshots, and old drafts.&lt;/p&gt;

&lt;p&gt;Opening one Markdown file is easy. Opening one HTML file is easy. Reviewing the whole set after an agent finishes a task is the annoying part.&lt;/p&gt;

&lt;p&gt;This is the workflow I use when I need to turn a pile of generated files into a clear decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from the folder
&lt;/h2&gt;

&lt;p&gt;Do not open random files one by one. Start from the project folder and look for the documents that changed recently.&lt;/p&gt;

&lt;p&gt;I usually ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Markdown and HTML files changed in this task?&lt;/li&gt;
&lt;li&gt;Which files look like plans, reports, summaries, or handoff notes?&lt;/li&gt;
&lt;li&gt;Which ones contain diagrams, tables, screenshots, or browser previews?&lt;/li&gt;
&lt;li&gt;Which ones are old drafts or generated noise?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That simple first pass keeps the review tied to the task. It also stops me from wasting time on a stale file just because it was the first one I noticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use different tools for different moments
&lt;/h2&gt;

&lt;p&gt;VS Code is still the best place to edit code. A browser is still the best quick way to open one HTML file. Finder is fine when I only need to check a file name.&lt;/p&gt;

&lt;p&gt;The gap appears when one agent task creates several docs across a project folder.&lt;/p&gt;

&lt;p&gt;In that moment, I do not want another editor. I want a calm reading surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open the folder
-&amp;gt; filter Markdown and HTML
-&amp;gt; read rendered docs
-&amp;gt; preview HTML safely
-&amp;gt; decide what to keep, fix, or ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the small job a local Markdown and HTML reader can do well.&lt;/p&gt;

&lt;h2&gt;
  
  
  My review flow
&lt;/h2&gt;

&lt;p&gt;When a task produces several generated files, I go through them in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the project folder.&lt;/li&gt;
&lt;li&gt;Filter to Markdown and HTML.&lt;/li&gt;
&lt;li&gt;Read the recently modified docs first.&lt;/li&gt;
&lt;li&gt;Read Markdown rendered, not raw.&lt;/li&gt;
&lt;li&gt;Preview HTML in a controlled local view.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;TBD&lt;/code&gt;, &lt;code&gt;draft&lt;/code&gt;, &lt;code&gt;fix later&lt;/code&gt;, &lt;code&gt;待确认&lt;/code&gt;, and similar markers.&lt;/li&gt;
&lt;li&gt;Put each file into one bucket: keep, fix, or ignore.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not to preserve every file an agent creates. The goal is to make a human decision quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I check before sharing
&lt;/h2&gt;

&lt;p&gt;Before I send an AI-generated doc to someone else, I check a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the doc say what decision or action it supports?&lt;/li&gt;
&lt;li&gt;Are code paths, dates, URLs, numbers, and product names still correct?&lt;/li&gt;
&lt;li&gt;Can someone scan the headings and understand the point?&lt;/li&gt;
&lt;li&gt;Do links, images, local references, and diagrams work?&lt;/li&gt;
&lt;li&gt;Does the HTML preview rely on missing or unsafe resources?&lt;/li&gt;
&lt;li&gt;Did the doc expose local paths, private notes, credentials, customer data, or internal-only context?&lt;/li&gt;
&lt;li&gt;Is the status clear: final, draft, or still waiting for confirmation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because generated docs can sound polished while still containing old names, broken links, or hidden assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a dedicated reader helps
&lt;/h2&gt;

&lt;p&gt;A dedicated reader is useful when Markdown and HTML need to be reviewed together, especially inside a code-heavy folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader&lt;/a&gt; is built for that moment. It opens local folders, filters Markdown and HTML, renders Markdown read-only, previews HTML in a sandboxed frame, and keeps recent documents close.&lt;/p&gt;

&lt;p&gt;It is not meant to replace an editor or a browser. It is meant to make the review step quieter.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple rule
&lt;/h2&gt;

&lt;p&gt;If I need to edit one Markdown file, I use my editor.&lt;/p&gt;

&lt;p&gt;If I need to open one HTML file, I use my browser.&lt;/p&gt;

&lt;p&gt;If an AI agent generated a set of Markdown and HTML files across a real project folder, I treat them as a document set and review them together.&lt;/p&gt;

&lt;p&gt;That small change saves a surprising amount of digging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/building-md-html-reader/" rel="noopener noreferrer"&gt;Building MD+HTML Reader from idea to public Mac app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/og-image-checker/" rel="noopener noreferrer"&gt;OG Image Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.markdownguide.org/basic-syntax/" rel="noopener noreferrer"&gt;Markdown Guide: Basic syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe" rel="noopener noreferrer"&gt;MDN: iframe element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tools</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to locate the input position on macOS, and what it took to get it right</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:23:10 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/how-to-locate-the-input-position-on-macos-and-what-it-took-to-get-it-right-3n5d</link>
      <guid>https://dev.to/ahab_indieseek/how-to-locate-the-input-position-on-macos-and-what-it-took-to-get-it-right-3n5d</guid>
      <description>&lt;p&gt;Paste Switch has a small overlay that appears when I press the shortcut to cycle through recent clipboard items. The overlay should appear near the place where text is being inserted, not near the mouse pointer, not at the center of the screen, and not on the wrong display.&lt;/p&gt;

&lt;p&gt;That sounds like a small UI detail. It was not.&lt;/p&gt;

&lt;p&gt;The hard part is that macOS does not give every app the same accessibility shape. A native text field, a browser text area, and an Electron app can all look like “the current input,” but they may expose different Accessibility attributes. Some give an exact caret rectangle. Some only expose the frame of the focused text element. Some return stale or incomplete values depending on focus timing.&lt;/p&gt;

&lt;p&gt;The final solution is not a single magic API. It is a small decision tree, backed by logs, with a clear boundary between “exact caret” and “good enough input box anchor.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The behavior I wanted
&lt;/h2&gt;

&lt;p&gt;The product behavior is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Press Command+Shift+V
  -&amp;gt; paste the latest clipboard item
  -&amp;gt; show a compact overlay near the active typing position
Keep Command+Shift held
  -&amp;gt; press V again to cycle to the next item
Release Command or Shift
  -&amp;gt; end the cycle and hide the overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlay is feedback, not a picker. It should help me see which clipboard item is active while I stay inside the app where I am typing.&lt;/p&gt;

&lt;p&gt;That means the anchor matters. If the overlay appears next to the mouse pointer, it feels random. If it appears on another monitor, it feels broken. If it appears over a system permission dialog, it becomes hostile. For this kind of utility, positioning is part of the core interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first principle: ask Accessibility for the focused input
&lt;/h2&gt;

&lt;p&gt;On macOS, the right starting point is the Accessibility API.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AXUIElementCreateSystemWide()
  -&amp;gt; AXFocusedUIElement
  -&amp;gt; AXSelectedTextRange
  -&amp;gt; AXBoundsForRange
  -&amp;gt; caret rectangle in screen coordinates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it works, this is the best answer. &lt;code&gt;AXSelectedTextRange&lt;/code&gt; tells you the current selection or insertion point. &lt;code&gt;AXBoundsForRange&lt;/code&gt; turns that range into a rectangle. If the range length is zero, the rectangle is the caret. If the API cannot return a zero-length caret rectangle, you can infer it from the adjacent character bounds.&lt;/p&gt;

&lt;p&gt;In Paste Switch, this is the first source attempted. The current code asks the system-wide focused element first, then falls back through the focused application and focused window. The logs record which source returned a rectangle, so I can tell whether a position came from &lt;code&gt;direct_caret&lt;/code&gt;, &lt;code&gt;text_rect&lt;/code&gt;, a descendant, or nothing.&lt;/p&gt;

&lt;p&gt;That logging turned out to be more important than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second principle: not every app gives a caret
&lt;/h2&gt;

&lt;p&gt;The first implementation assumed: if there is a focused text input, there should be a caret rectangle.&lt;/p&gt;

&lt;p&gt;That assumption was wrong.&lt;/p&gt;

&lt;p&gt;In native macOS fields, &lt;code&gt;AXSelectedTextRange&lt;/code&gt; plus &lt;code&gt;AXBoundsForRange&lt;/code&gt; can work very well. In some browser and Electron surfaces, the focused element may report that it is a text input, but the parameterized bounds call returns nothing useful. Codex was the clearest example during testing: the app had a real focused input, but the exact caret bounds path was not reliable.&lt;/p&gt;

&lt;p&gt;If the code treats “no caret bounds” as “no input position,” the overlay falls back to the wrong place. In one broken version, it drifted toward the previous input location or the current screen center. That made the whole feature feel worse than before.&lt;/p&gt;

&lt;p&gt;The fix was to keep a second source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If exact caret bounds exist:
  use the caret rectangle
Else if the focused element is a text input:
  use AXFrame
  or AXPosition + AXSize
Else:
  keep searching descendants / windows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not pretending the input box frame is the caret. It is a named fallback with a different meaning. For a compact overlay, anchoring near the focused text field is still much better than anchoring near the mouse or the last known position.&lt;/p&gt;

&lt;p&gt;That distinction mattered. The bug was not “Codex has no input position.” The real issue was that Codex did not expose the exact caret through the same path as native fields, and the implementation had become too strict.&lt;/p&gt;

&lt;h2&gt;
  
  
  The third principle: do not mix coordinate systems casually
&lt;/h2&gt;

&lt;p&gt;Another bad turn came from trying to position the overlay with an AppKit window path.&lt;/p&gt;

&lt;p&gt;Accessibility returns screen-like coordinates. Tauri also has its own window positioning API, monitor work areas, physical pixels, logical pixels, and scale factors. AppKit uses a different coordinate origin convention. On a single screen this can look almost right. On multiple monitors, especially with different scale factors, it can become visibly wrong.&lt;/p&gt;

&lt;p&gt;The version that tried to push the overlay through an AppKit &lt;code&gt;setFrameTopLeftPoint&lt;/code&gt; style path made Codex look better for a moment, then broke other apps. The overlay was no longer reliably near the input position in normal browser and native app scenarios.&lt;/p&gt;

&lt;p&gt;The stable version went back to a simpler rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use Accessibility only to get the anchor rectangle.
Use the Tauri window API to position the overlay.
Convert through the target monitor's work area and scale factor.
Clamp the overlay inside that monitor.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation made the code easier to reason about. Accessibility answers “where is the input?” Tauri answers “where can this webview window be placed on this monitor?”&lt;/p&gt;

&lt;h2&gt;
  
  
  The current decision tree
&lt;/h2&gt;

&lt;p&gt;The current implementation is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On the first trigger of a new cycle:
  clear the locked overlay position
  read the focused input anchor on a short background lookup
  log every Accessibility source that was tried
  position the overlay once
  lock that overlay position for the active cycle

While Command+Shift remains held:
  pressing V cycles items
  the overlay content updates
  the overlay position does not jump

When Command or Shift is released:
  end the cycle
  hide the overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The anchor lookup has a timeout and an in-flight guard. That is important because Accessibility calls are crossing process boundaries. A clipboard utility should never let a slow or stuck target app block the whole machine.&lt;/p&gt;

&lt;p&gt;The actual anchor priority is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. focused element direct caret
2. focused text element frame
3. focused descendants
4. focused app window paths
5. controlled fallback only when no usable anchor exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important part is that each source is visible in logs. When a real app behaves differently, I want to see the reason instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs that made the root cause clearer
&lt;/h2&gt;

&lt;p&gt;Several wrong fixes were useful because they exposed the real shape of the problem.&lt;/p&gt;

&lt;p&gt;One wrong fix removed or de-emphasized the focused text element frame fallback. That made Electron-like apps worse. The code was demanding exact caret geometry from apps that did not provide it.&lt;/p&gt;

&lt;p&gt;Another wrong fix moved the overlay positioning into an AppKit path. That created a broader regression: other apps that used to position correctly now became wrong. The issue was not Accessibility anymore. It was coordinate conversion.&lt;/p&gt;

&lt;p&gt;A third mistake was treating “use the input field frame” as an ugly fallback to avoid. In practice, it is a valid source with a different precision level. The product needs to know which one it got, but it should not reject a usable input box just because the exact caret is unavailable.&lt;/p&gt;

&lt;p&gt;The final stable behavior came from restoring that layered model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exact caret when available
focused text field frame when exact caret is unavailable
Tauri monitor positioning for the overlay window
logs that say which path was used
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What working with an AI agent changed
&lt;/h2&gt;

&lt;p&gt;This feature was implemented with an AI coding agent in the loop. That helped a lot, but it also created a few sharp edges.&lt;/p&gt;

&lt;p&gt;The helpful part was speed. The agent could inspect the Rust and TypeScript paths, add logging, change the overlay positioning code, rebuild the app, and wire the UI quickly. For a macOS utility with a lot of small edge cases, that speed is valuable.&lt;/p&gt;

&lt;p&gt;The dangerous part was confidence without enough evidence.&lt;/p&gt;

&lt;p&gt;At one point, the agent concluded a fix was correct because the code path looked plausible. I tested it in Codex, and it was still wrong. Then another fix improved Codex but broke other apps. That is the exact failure mode you have to watch for when using an AI agent on UI and system integration work: it can optimize for the latest failing example and accidentally destroy a previously working path.&lt;/p&gt;

&lt;p&gt;The process got better after I forced a stricter loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do not guess from the code shape alone.
Add logs at the boundary where the system returns data.
Name every fallback source.
Compare native apps, browser apps, and Electron apps separately.
Do not claim success until the behavior is manually verified.
When a fix broadens the blast radius, stop and restore the known-good part.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most useful logs were not verbose debug dumps. They were small structured traces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;focused app pid / role / window state
focused element role / subrole
AXSelectedTextRange availability
AXBoundsForRange result
AXFrame result
AXPosition + AXSize result
chosen source
final anchor rectangle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those logs changed the conversation with the agent. Instead of arguing about theories, we could ask: did Codex return a caret? Did it return a text frame? Did the overlay anchor get converted to the correct monitor?&lt;/p&gt;

&lt;p&gt;That is where the AI agent became useful again. Once the unknowns were observable, the agent could modify the right layer instead of patching around symptoms.&lt;/p&gt;

&lt;h2&gt;
  
  
  My practical rule for AI-assisted system work
&lt;/h2&gt;

&lt;p&gt;AI agents are strongest when the task has a tight feedback loop and clear evidence. They are weakest when a bug is visible only in a real desktop interaction and the agent has to infer too much from code.&lt;/p&gt;

&lt;p&gt;For this kind of work, I now use a stricter rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First make the hidden system state observable.
Then change the smallest layer that explains the observed state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Paste Switch, that meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;log Accessibility results before changing positioning;&lt;/li&gt;
&lt;li&gt;separate caret lookup from overlay window placement;&lt;/li&gt;
&lt;li&gt;keep fallback behavior explicit instead of hiding it behind generic retry logic;&lt;/li&gt;
&lt;li&gt;verify in at least one native app, one browser surface, one Electron surface, and a multi-monitor setup;&lt;/li&gt;
&lt;li&gt;treat “it works here” as a local observation, not a global conclusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final implementation is not fancy. It is careful.&lt;/p&gt;

&lt;p&gt;It asks macOS for the most precise caret position it can get. When macOS or the target app cannot provide that, it uses the focused input box as the anchor. It keeps coordinate conversion in one place. It logs the source. It locks the position for one shortcut cycle so the overlay does not jump while I keep Command+Shift held.&lt;/p&gt;

&lt;p&gt;That is the difference between an overlay that feels random and one that feels attached to where I am actually typing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Precise input positioning on macOS is not one API call. It is a layered strategy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accessibility caret bounds
  -&amp;gt; focused text element frame
  -&amp;gt; descendant/window search
  -&amp;gt; monitor-aware overlay placement
  -&amp;gt; logs for every real app that disagrees with your assumptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI agent helped move quickly, but the key was not letting it keep guessing. The real breakthrough came from making the OS boundary visible and forcing the implementation to respect what the target app actually exposed.&lt;/p&gt;

&lt;p&gt;That is probably the broader lesson too. AI can write code fast. For system-level product details, correctness still comes from patient observation, tight feedback, and a human who refuses to accept a plausible explanation until the product actually behaves correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/paste-switch/" rel="noopener noreferrer"&gt;Paste Switch product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/paste-switch-macos-app-development/" rel="noopener noreferrer"&gt;Building Paste Switch as a Mac app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/applicationservices/axuielement" rel="noopener noreferrer"&gt;Apple AXUIElement documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/appkit/nsaccessibility" rel="noopener noreferrer"&gt;Apple NSAccessibility documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>debugging</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>Codex Record &amp; Replay principles</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:17:53 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/codex-record-replay-principles-3j6d</link>
      <guid>https://dev.to/ahab_indieseek/codex-record-replay-principles-3j6d</guid>
      <description>&lt;p&gt;Codex Record &amp;amp; Replay is interesting because it starts from a very human habit: when something is hard to describe, show it once.&lt;/p&gt;

&lt;p&gt;Instead of writing a long prompt that explains every click, window switch, preference, and edge case, the user records a workflow on macOS. Codex observes the workflow, reads the event trace, and drafts a reusable Skill.&lt;/p&gt;

&lt;p&gt;The key idea is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human demonstration
-&amp;amp;gt; structured event trace
-&amp;amp;gt; reusable Skill
-&amp;amp;gt; semantic replay
-&amp;amp;gt; verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recorded trace is not the final automation. It is evidence. The reusable artifact is the Skill.&lt;/p&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is not a coordinate macro
&lt;/h2&gt;

&lt;p&gt;Traditional macro recorders tend to remember low-level actions: move here, click there, type this, wait a bit.&lt;/p&gt;

&lt;p&gt;That breaks quickly. A button moves. A page changes. A window opens in a different size. A user is already logged in on one run and logged out on another.&lt;/p&gt;

&lt;p&gt;Record &amp;amp; Replay points to a better model. The recording helps Codex understand the workflow, but the replay should use the most stable tool available.&lt;/p&gt;

&lt;p&gt;If there is an API, use the API.&lt;br&gt;&lt;br&gt;
If there is a browser target, use browser automation.&lt;br&gt;&lt;br&gt;
If there is a desktop UI, use Computer Use.&lt;br&gt;&lt;br&gt;
If something changed, verify the state and adapt.&lt;/p&gt;

&lt;p&gt;That is why the product is closer to workflow learning than to mouse replay.&lt;/p&gt;
&lt;h2&gt;
  
  
  The public shape
&lt;/h2&gt;

&lt;p&gt;From the public Codex documentation, the flow is roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user starts recording from Codex.&lt;/li&gt;
&lt;li&gt;The user performs a workflow on macOS.&lt;/li&gt;
&lt;li&gt;Codex captures actions and window context through Computer Use.&lt;/li&gt;
&lt;li&gt;The recording is stopped.&lt;/li&gt;
&lt;li&gt;Codex reads the captured trace.&lt;/li&gt;
&lt;li&gt;Codex drafts a Skill that can be inspected, edited, and reused.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is useful when the workflow is easier to show than to describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeated UI workflows;&lt;/li&gt;
&lt;li&gt;personal or team preferences;&lt;/li&gt;
&lt;li&gt;tools that do not have a good API;&lt;/li&gt;
&lt;li&gt;tasks that cross browser pages, desktop apps, files, and dialogs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The promise is not “Codex remembers every pixel.” The promise is “Codex learns enough from the demonstration to create a reusable operating guide.”&lt;/p&gt;
&lt;h2&gt;
  
  
  What the local plugin suggests
&lt;/h2&gt;

&lt;p&gt;The local Codex app includes a bundled &lt;code&gt;record-and-replay&lt;/code&gt; plugin. The plugin exposes an &lt;code&gt;event-stream&lt;/code&gt; MCP server through the Computer Use helper.&lt;/p&gt;

&lt;p&gt;The bundled Skill tells Codex to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start recording with &lt;code&gt;event_stream_start&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;check status with &lt;code&gt;event_stream_status&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;stop recording with &lt;code&gt;event_stream_stop&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;read the returned &lt;code&gt;metadataPath&lt;/code&gt; and &lt;code&gt;eventsPath&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;treat &lt;code&gt;events.jsonl&lt;/code&gt; as the primary evidence;&lt;/li&gt;
&lt;li&gt;treat &lt;code&gt;session.json&lt;/code&gt; as timing and path metadata;&lt;/li&gt;
&lt;li&gt;use the recording as evidence of intent, not as a command to replay every UI action exactly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the heart of the design.&lt;/p&gt;

&lt;p&gt;String inspection of the helper binary also shows names such as &lt;code&gt;event_stream_start&lt;/code&gt;, &lt;code&gt;event_stream_status&lt;/code&gt;, &lt;code&gt;event_stream_stop&lt;/code&gt;, &lt;code&gt;eventsPath&lt;/code&gt;, &lt;code&gt;metadataPath&lt;/code&gt;, &lt;code&gt;suppressedEventsPath&lt;/code&gt;, &lt;code&gt;AXUIElement&lt;/code&gt;, and &lt;code&gt;screenRecordingGranted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That does not reveal the private implementation, but it matches the public shape: macOS Accessibility, input/window context, screen context, local trace files, and a Skill compiler.&lt;/p&gt;
&lt;h2&gt;
  
  
  The architecture in one picture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  U["User demonstrates workflow"] --&amp;amp;gt; P["Record &amp;amp;amp; Replay plugin"]
  P --&amp;amp;gt; MCP["event-stream MCP server"]
  MCP --&amp;amp;gt; OS["macOS Accessibility\ninput events\nscreen context"]
  OS --&amp;amp;gt; E["session.json\nevents.jsonl"]
  E --&amp;amp;gt; C["Codex analyzes trace\nintent, inputs, steps, verification"]
  C --&amp;amp;gt; S["Reusable SKILL.md"]
  S --&amp;amp;gt; R["New thread uses the Skill"]
  R --&amp;amp;gt; T["Computer Use\nBrowser\nConnector\nCLI / API"]
  T --&amp;amp;gt; V["Verify result"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important split is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Record captures what happened.&lt;/li&gt;
&lt;li&gt;Trace stores the evidence.&lt;/li&gt;
&lt;li&gt;Compile turns noisy evidence into a readable Skill.&lt;/li&gt;
&lt;li&gt;Replay executes the Skill with the best available tools.&lt;/li&gt;
&lt;li&gt;Verify checks whether the result is actually correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without compile and verify, the system becomes a brittle macro recorder.&lt;/p&gt;
&lt;h2&gt;
  
  
  What an event trace needs to contain
&lt;/h2&gt;

&lt;p&gt;The public docs do not publish a full event schema, but a useful trace needs more than clicks.&lt;/p&gt;

&lt;p&gt;It should preserve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mouse and keyboard actions;&lt;/li&gt;
&lt;li&gt;foreground app and window context;&lt;/li&gt;
&lt;li&gt;browser URL and page state;&lt;/li&gt;
&lt;li&gt;Accessibility targets such as buttons, fields, menus, and selected objects;&lt;/li&gt;
&lt;li&gt;text input and selection;&lt;/li&gt;
&lt;li&gt;screenshots or keyframes when structure is incomplete;&lt;/li&gt;
&lt;li&gt;start, stop, cancel, timeout, and redaction boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An event can be modeled like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-22T10:00:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"desktop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mouse.click"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google Chrome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bundleIdentifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.google.Chrome"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Console"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/admin"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AXButton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bounds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1040&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not perfect low-level playback. The point is enough evidence for Codex to infer the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skill compilation is the hard part
&lt;/h2&gt;

&lt;p&gt;Raw traces are noisy. They include waits, focus changes, scrolls, repeated clicks, and accidental actions.&lt;/p&gt;

&lt;p&gt;The compiler has to compress that into a Skill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
  A["events.jsonl"] --&amp;amp;gt; B["Clean noise"]
  B --&amp;amp;gt; C["Infer task goal"]
  C --&amp;amp;gt; D["Extract variables"]
  D --&amp;amp;gt; E["Generate semantic steps"]
  E --&amp;amp;gt; F["Define verification"]
  F --&amp;amp;gt; G["Add fallback handling"]
  G --&amp;amp;gt; H["SKILL.md"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good Skill should say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when to use it;&lt;/li&gt;
&lt;li&gt;what inputs change between runs;&lt;/li&gt;
&lt;li&gt;what login, permissions, pages, files, or apps are needed;&lt;/li&gt;
&lt;li&gt;which steps matter;&lt;/li&gt;
&lt;li&gt;how to verify success;&lt;/li&gt;
&lt;li&gt;what to do when the UI changes;&lt;/li&gt;
&lt;li&gt;what sensitive data must not be stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the main difference from classic RPA. RPA often tries to replay actions. Record &amp;amp; Replay tries to replay intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replay should use the strongest path
&lt;/h2&gt;

&lt;p&gt;Replay does not mean reading &lt;code&gt;events.jsonl&lt;/code&gt; and executing every line.&lt;/p&gt;

&lt;p&gt;When the user asks for a similar task later, Codex should load the Skill, resolve inputs and preconditions, then choose the most stable path for each step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
  A["User asks for a similar task"] --&amp;amp;gt; B["Codex loads matching Skill"]
  B --&amp;amp;gt; C["Resolve inputs and preconditions"]
  C --&amp;amp;gt; D{"Stable semantic tool available?"}
  D -- "Yes" --&amp;amp;gt; E["Use connector, CLI, API, or browser automation"]
  D -- "No" --&amp;amp;gt; F["Use Computer Use on the UI"]
  E --&amp;amp;gt; G["Verify result"]
  F --&amp;amp;gt; G
  G --&amp;amp;gt; H{"Verified?"}
  H -- "Yes" --&amp;amp;gt; I["Return output"]
  H -- "No" --&amp;amp;gt; J["Retry, adapt, or report blocker"]
  J --&amp;amp;gt; G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes replay more resilient. If a button moves but keeps the same accessible name, Codex can still find it. If a connector or API exists, Codex can avoid the UI entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I would build a similar MVP
&lt;/h2&gt;

&lt;p&gt;I would not start with a browser-only recorder. The interesting part is that Record &amp;amp; Replay can learn real computer workflows.&lt;/p&gt;

&lt;p&gt;The smallest credible MVP needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a browser recorder for URL, DOM targets, ARIA role/name, selectors, input, navigation, and screenshots;&lt;/li&gt;
&lt;li&gt;a computer recorder for foreground app, window, Accessibility target, mouse, keyboard, selection, and screenshots;&lt;/li&gt;
&lt;li&gt;one unified &lt;code&gt;events.jsonl&lt;/code&gt; timeline with a &lt;code&gt;surface&lt;/code&gt; field;&lt;/li&gt;
&lt;li&gt;a compiler that turns the trace into &lt;code&gt;routine.md&lt;/code&gt; or &lt;code&gt;SKILL.md&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a runtime that can choose browser, desktop, CLI, API, or connector per step;&lt;/li&gt;
&lt;li&gt;a verifier that checks DOM, Accessibility state, screenshots, file output, or API result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Record -&amp;amp;gt; Trace -&amp;amp;gt; Compile -&amp;amp;gt; Execute -&amp;amp;gt; Verify -&amp;amp;gt; Improve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else is detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety is not optional
&lt;/h2&gt;

&lt;p&gt;A system that records real workflows will see sensitive information.&lt;/p&gt;

&lt;p&gt;Minimum rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make the recording scope visible before recording starts;&lt;/li&gt;
&lt;li&gt;allow stop and cancel at any time;&lt;/li&gt;
&lt;li&gt;store traces locally by default;&lt;/li&gt;
&lt;li&gt;redact passwords, tokens, keys, payment data, and private identifiers;&lt;/li&gt;
&lt;li&gt;treat page and window text as untrusted input;&lt;/li&gt;
&lt;li&gt;require confirmation for irreversible, payment, permission, or destructive actions;&lt;/li&gt;
&lt;li&gt;never write real credentials into generated routines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompt injection also matters. A recorded page may contain text that tries to instruct the model. The compiler must treat page text as data, not as instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Codex Record &amp;amp; Replay is best understood as a workflow-learning system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observe a real workflow
-&amp;amp;gt; store structured evidence
-&amp;amp;gt; compile the evidence into a Skill
-&amp;amp;gt; replay the Skill semantically
-&amp;amp;gt; verify the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical lesson is clear: do not build a mouse recorder. Build a trace system, a compiler, a semantic runtime, and a verification loop.&lt;/p&gt;

&lt;p&gt;That is what makes Record &amp;amp; Replay meaningfully different from ordinary automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI Codex Record &amp;amp; Replay documentation: &lt;/li&gt;
&lt;li&gt;OpenAI Community announcement: &lt;/li&gt;
&lt;li&gt;Azuki analysis: &lt;/li&gt;
&lt;li&gt;OpenAI Codex GitHub issue: &lt;/li&gt;
&lt;li&gt;OpenBrowser: &lt;/li&gt;
&lt;li&gt;Interceptor: &lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/record-replay-macos-app-technical-plan/" rel="noopener noreferrer"&gt;Record &amp;amp; Replay macOS app technical plan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Local evidence: the bundled Codex.app &lt;code&gt;record-and-replay&lt;/code&gt; plugin, its &lt;code&gt;.mcp.json&lt;/code&gt;, and its included Skill&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI search SEO checklist for indie sites: what Google's 2026 guide actually changes</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 14:17:49 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/ai-search-seo-checklist-for-indie-sites-what-googles-2026-guide-actually-changes-2g39</link>
      <guid>https://dev.to/ahab_indieseek/ai-search-seo-checklist-for-indie-sites-what-googles-2026-guide-actually-changes-2g39</guid>
      <description>&lt;h2&gt;
  
  
  Quick answer
&lt;/h2&gt;

&lt;p&gt;If you run a small indie site, AI search does not replace SEO. Google's own guidance says generative AI features in Search still depend on the core Search index, crawlability, snippets, and quality systems.&lt;/p&gt;

&lt;p&gt;The practical change is not "write for AI" or publish more pages. The change is to make every page easier for humans, search crawlers, and AI systems to understand: clear purpose, original value, crawlable HTML, trustworthy sources, useful structure, and no low-value content automation.&lt;/p&gt;

&lt;p&gt;For an indie site, the best AI search SEO workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one real user problem
-&amp;gt; one useful page
-&amp;gt; original checklist or workflow
-&amp;gt; clear metadata and internal links
-&amp;gt; human review before publishing
-&amp;gt; sitemap and indexing checks after deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;This guide is for solo founders, indie hackers, and small product teams with a real site but little search data yet. You might have a product page, a few blog posts, some tools, and a sitemap, but Search Console still shows almost nothing.&lt;/p&gt;

&lt;p&gt;That is the awkward early stage: you need content to get discovered, but publishing a lot of AI-assisted pages can also create thin, forgettable content.&lt;/p&gt;

&lt;p&gt;The goal is not to trick AI Overviews or AI Mode. The goal is to publish pages that are worth indexing and worth citing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in 2026
&lt;/h2&gt;

&lt;p&gt;Google's 2026 guidance on generative AI features makes three things clear.&lt;/p&gt;

&lt;p&gt;First, SEO still matters. AI Overviews and AI Mode are rooted in Google's Search index and quality systems. If your content is not crawlable, indexable, and eligible for snippets, it is not in a strong position for AI search either.&lt;/p&gt;

&lt;p&gt;Second, "AEO" and "GEO" are not magic layers above SEO. From Google's point of view, optimizing for generative AI search is still optimizing for Search. The basics still matter: valuable content, technical clarity, snippets, images where useful, page experience, and reduced duplication.&lt;/p&gt;

&lt;p&gt;Third, commodity content is weak. A generic "7 tips" article that could come from any site does not give Google or readers much reason to trust it. A first-hand checklist, a real workflow, a teardown, a template, or a decision tree is stronger because it adds something beyond public summaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The indie-site decision tree
&lt;/h2&gt;

&lt;p&gt;Use this before creating a new article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is there a real user problem?
  No -&amp;gt; do not publish.
  Yes -&amp;gt; continue.

Can you add original value?
  No -&amp;gt; save as research notes.
  Yes -&amp;gt; continue.

Is there already a close article on your site?
  Yes -&amp;gt; update that page instead.
  No -&amp;gt; continue.

Can the article include a checklist, template, test, example, or decision rule?
  No -&amp;gt; wait until you can add one.
  Yes -&amp;gt; draft it.

Would a reader feel done after reading it?
  No -&amp;gt; revise.
  Yes -&amp;gt; publish after review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one decision tree prevents the most common cold-start SEO mistake: creating many small pages that all chase nearby long-tail terms but do not fully solve anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 10-minute AI search SEO audit
&lt;/h2&gt;

&lt;p&gt;Before publishing, check the page in this order.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: The title should answer a task, not just contain a keyword.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Original increment&lt;/strong&gt;: The page should contain a template, checklist, example, test result, decision rule, or first-hand judgment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawlability&lt;/strong&gt;: The main content should be available in normal HTML, not hidden behind blocked JavaScript or a login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snippet readiness&lt;/strong&gt;: The first section should give a concise answer that can stand alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust&lt;/strong&gt;: Add sources when claims depend on external facts, especially for Google, security, pricing, or platform behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal links&lt;/strong&gt;: Link to the next useful page, not just to pages you want to promote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata&lt;/strong&gt;: Check the title, meta description, canonical path, Open Graph image, and social preview. IndieSeek has a &lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt; and &lt;a href="https://indieseek.co/tools/og-image-checker/" rel="noopener noreferrer"&gt;OG Image Checker&lt;/a&gt; for this step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate risk&lt;/strong&gt;: If the topic overlaps an older article, update the older article or merge the new section into it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human review&lt;/strong&gt;: Read the final Markdown or HTML as a reader would. If the article came from an AI-assisted workflow, review it before it reaches the live site. A local reader such as &lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader&lt;/a&gt; is useful for this kind of review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indexing workflow&lt;/strong&gt;: After deployment, confirm the page is in the sitemap and run your normal search submission or inspection steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What to do about llms.txt
&lt;/h2&gt;

&lt;p&gt;Do not create &lt;code&gt;llms.txt&lt;/code&gt; only because you think Google needs it.&lt;/p&gt;

&lt;p&gt;Google's AI search guidance says you do not need special AI text files or machine-readable markup to appear in Google Search's generative AI features. It also says keeping such files is fine for other services or systems, but it does not help or hurt Google Search visibility.&lt;/p&gt;

&lt;p&gt;For an indie site, the practical rule is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Search / AI Overviews&lt;/td&gt;
&lt;td&gt;Prioritize crawlable HTML, sitemap, snippets, internal links, and high-quality content.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser agents or coding agents reading your docs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;llms.txt&lt;/code&gt; may be useful as an agent usability file, but treat it as optional.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trying to rank by creating AI-only files&lt;/td&gt;
&lt;td&gt;Do not do this. It is not a durable strategy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you create &lt;code&gt;llms.txt&lt;/code&gt;, make it accurate and maintainable. Do not let it become a stale second version of your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The publish gate for AI-assisted content
&lt;/h2&gt;

&lt;p&gt;AI can help research, outline, and draft. It should not be the final quality owner.&lt;/p&gt;

&lt;p&gt;Before publishing an AI-assisted article, require these checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the article solve a real task for a defined reader?&lt;/li&gt;
&lt;li&gt;Does it add something that was not already obvious from the sources?&lt;/li&gt;
&lt;li&gt;Are all current facts sourced and dated where needed?&lt;/li&gt;
&lt;li&gt;Are claims about Google, AI search, or platform behavior cautious and verifiable?&lt;/li&gt;
&lt;li&gt;Is the Chinese version a full natural article, not a summary?&lt;/li&gt;
&lt;li&gt;Would you still publish this if search traffic did not exist?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is the hardest one. If the answer is no, the page is probably search-engine-first content. Keep it as a draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;The first mistake is publishing one page per keyword variation. Google can understand related meanings. Users do not need five thin pages for one problem.&lt;/p&gt;

&lt;p&gt;The second mistake is rewriting for AI systems instead of humans. Google says there is no special writing style required for generative AI search. Clear writing helps because it helps people.&lt;/p&gt;

&lt;p&gt;The third mistake is treating structured data as a shortcut. Structured data can help with rich results, but it does not replace useful content.&lt;/p&gt;

&lt;p&gt;The fourth mistake is changing dates to make old content look fresh. Update pages when you have a substantial improvement; do not fake freshness.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is AI search SEO different from normal SEO?
&lt;/h3&gt;

&lt;p&gt;It is mostly normal SEO applied to a search experience that may summarize, cite, and fan out queries. Crawlability, page quality, metadata, internal links, and trust still matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should a new indie site chase AI Overviews?
&lt;/h3&gt;

&lt;p&gt;Not directly. A better early target is to publish specific, useful pages that solve clear user problems. If the content is strong enough for users and Google Search, it is in a better position for AI search features too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I publish every day?
&lt;/h3&gt;

&lt;p&gt;Only if every page clears the quality bar. Daily research is fine. Daily publishing is risky if it creates thin or repetitive content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need llms.txt?
&lt;/h3&gt;

&lt;p&gt;Not for Google Search visibility. It may help some agents understand your site, but it should be optional and accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Search Central: &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Search Central Blog: &lt;a href="https://developers.google.com/search/blog/2026/05/a-new-resource-for-optimizing" rel="noopener noreferrer"&gt;https://developers.google.com/search/blog/2026/05/a-new-resource-for-optimizing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Helpful content guidance: &lt;a href="https://developers.google.com/search/docs/fundamentals/creating-helpful-content" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/fundamentals/creating-helpful-content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI-generated content guidance: &lt;a href="https://developers.google.com/search/docs/fundamentals/using-gen-ai-content" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/fundamentals/using-gen-ai-content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spam policies / scaled content abuse: &lt;a href="https://developers.google.com/search/docs/essentials/spam-policies#scaled-content" rel="noopener noreferrer"&gt;https://developers.google.com/search/docs/essentials/spam-policies#scaled-content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/og-image-checker/" rel="noopener noreferrer"&gt;OG Image Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HN discussion on agent-native SEO tooling: &lt;a href="https://news.ycombinator.com/item?id=48592731" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=48592731&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HN discussion on AI visibility workflows: &lt;a href="https://news.ycombinator.com/item?id=47457472" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=47457472&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HN discussion on AI crawler behavior: &lt;a href="https://news.ycombinator.com/item?id=48677487" rel="noopener noreferrer"&gt;https://news.ycombinator.com/item?id=48677487&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building MD+HTML Reader from a personal itch to a public macOS app</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 12:38:15 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/building-mdhtml-reader-from-a-personal-itch-to-a-public-macos-app-2p00</link>
      <guid>https://dev.to/ahab_indieseek/building-mdhtml-reader-from-a-personal-itch-to-a-public-macos-app-2p00</guid>
      <description>&lt;p&gt;I did not build MD+HTML Reader because I wanted to make another Markdown app.&lt;/p&gt;

&lt;p&gt;It started with a small daily irritation. AI coding agents kept leaving useful documents inside my project folders: plans, implementation notes, release checklists, Mermaid diagrams, and HTML previews. The files mattered, but they were buried next to source code, configs, dependencies, and build output.&lt;/p&gt;

&lt;p&gt;Opening one file was easy. Reviewing the whole task was not.&lt;/p&gt;

&lt;p&gt;I kept switching between VS Code previews, browser tabs, Finder, recent files, and random Markdown viewers. None of those tools were wrong. They just were not designed for the moment after an agent finishes a task and a human has to read the output.&lt;/p&gt;

&lt;p&gt;That became the shape of MD+HTML Reader: a quiet Mac app for reviewing local Markdown and HTML files, not editing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  I kept the product narrow
&lt;/h2&gt;

&lt;p&gt;The app is not trying to replace VS Code, Obsidian, Typora, Finder, or the browser.&lt;/p&gt;

&lt;p&gt;The workflow is much smaller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;open a local folder
-&amp;gt; filter Markdown and HTML
-&amp;gt; read rendered docs without editing source files
-&amp;gt; preview Mermaid and HTML artifacts
-&amp;gt; get back to recent work quickly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boundary helped a lot. As a solo builder, every extra promise turns into development, support, copywriting, screenshots, tests, and bugs. The first version had to be small enough to ship, but clear enough that a stranger could understand why it exists.&lt;/p&gt;

&lt;p&gt;The public promise became simple: local-first reading for AI-generated Markdown and HTML inside project folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The app working was only the first step
&lt;/h2&gt;

&lt;p&gt;The first version was useful to me once it could open folders, filter docs, render Markdown, show Mermaid diagrams, preview HTML, and remember recent documents.&lt;/p&gt;

&lt;p&gt;But a public product needs more than the main feature.&lt;/p&gt;

&lt;p&gt;I still had to build the path around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a product page that explains the pain in plain English;&lt;/li&gt;
&lt;li&gt;pricing that makes the trial, monthly plan, and lifetime option easy to understand;&lt;/li&gt;
&lt;li&gt;privacy and terms pages that explain local document handling;&lt;/li&gt;
&lt;li&gt;Creem checkout and license activation;&lt;/li&gt;
&lt;li&gt;app and website analytics that avoid local file names and document content;&lt;/li&gt;
&lt;li&gt;signed and notarized macOS builds;&lt;/li&gt;
&lt;li&gt;a DMG, updater files, release notes, and live download checks;&lt;/li&gt;
&lt;li&gt;screenshots, demo assets, launch copy, and blog posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the uncomfortable lesson: “it works on my machine” is not a product. It becomes a product when someone else can find it, download it, trust it, pay for it, and update it without me sitting beside them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment was more than a checkout link
&lt;/h2&gt;

&lt;p&gt;I chose a self-serve path: free trial first, then a low-priced monthly plan or lifetime access. The goal was not perfect pricing. The goal was to learn whether anyone would pay for this workflow at all.&lt;/p&gt;

&lt;p&gt;The payment work had more pieces than I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checkout links;&lt;/li&gt;
&lt;li&gt;license key activation inside the app;&lt;/li&gt;
&lt;li&gt;backend license validation;&lt;/li&gt;
&lt;li&gt;local entitlement state;&lt;/li&gt;
&lt;li&gt;expired and invalid license states;&lt;/li&gt;
&lt;li&gt;test and live environments;&lt;/li&gt;
&lt;li&gt;support copy for the wrong email or a missing key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product rule was clear: a paid user should not need me to manually unlock the app. If every purchase creates manual work, the product is not shaped correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics had to stay boring
&lt;/h2&gt;

&lt;p&gt;MD+HTML Reader is local-first, so analytics had to be boring on purpose.&lt;/p&gt;

&lt;p&gt;On the website, I care about visits, downloads, pricing views, checkout clicks, and source attribution. In the app, I care about opens, daily activity, trial locks, checkout clicks, license activation, and invite actions.&lt;/p&gt;

&lt;p&gt;I do not want local paths, file names, or document content in analytics. That boundary is part of the product, not just a privacy sentence.&lt;/p&gt;

&lt;p&gt;It also changes how I look at traffic. A spike is not enough. I need to know whether people downloaded, opened the app, returned, clicked pricing, and activated a license.&lt;/p&gt;

&lt;h2&gt;
  
  
  macOS release work is a product of its own
&lt;/h2&gt;

&lt;p&gt;Shipping a macOS app publicly is a separate project.&lt;/p&gt;

&lt;p&gt;The app has to run on Apple Silicon and Intel Macs. It needs Developer ID signing. The DMG needs notarization. Gatekeeper has to accept the downloaded file. Auto-update needs a signed archive, a signature, and a live &lt;code&gt;latest.json&lt;/code&gt; manifest.&lt;/p&gt;

&lt;p&gt;I also learned that release notes are part of the product. If the app shows an update prompt, that sentence should be short, clear, and checked before packaging.&lt;/p&gt;

&lt;p&gt;The release flow became a repeatable path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bump version
-&amp;gt; build signed app
-&amp;gt; notarize and staple
-&amp;gt; generate DMG and updater files
-&amp;gt; build website download assets
-&amp;gt; deploy
-&amp;gt; verify the live page, download, latest.json, hashes, and updater checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is repetitive, but that is why it is useful. Every release should make the next release safer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promotion forced better wording
&lt;/h2&gt;

&lt;p&gt;Promotion is not just “posting after launch.”&lt;/p&gt;

&lt;p&gt;The product page, screenshots, demo video, Product Hunt copy, Show HN post, Reddit angle, directory listing, and SEO post all ask the same question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can a stranger understand why this exists?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“A Markdown and HTML reader” is too broad.&lt;/p&gt;

&lt;p&gt;The clearer angle is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review AI-generated Markdown and HTML without code-file clutter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sentence says who it is for and when it helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want to reuse
&lt;/h2&gt;

&lt;p&gt;The most useful part of MD+HTML Reader may be the system left around it.&lt;/p&gt;

&lt;p&gt;For the next product, I do not want to start from zero. I want to reuse the product page structure, pricing and license flow, analytics events, release scripts, launch checklist, promo asset structure, and blog format.&lt;/p&gt;

&lt;p&gt;That is the bigger lesson for me: one small product should leave behind infrastructure, language, and distribution learning for the next one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the bet stands
&lt;/h2&gt;

&lt;p&gt;MD+HTML Reader is still early. I am not writing this as a victory lap.&lt;/p&gt;

&lt;p&gt;The bet is simple: AI agents will keep creating local project documents, and people will still need a calm way to review them.&lt;/p&gt;

&lt;p&gt;If that is wrong, the product should teach me quickly. If it is right, the same product system can support the next app with less manual work.&lt;/p&gt;

&lt;p&gt;That is the kind of indie product I want to build: small, useful, and able to make the next product easier.&lt;/p&gt;

&lt;p&gt;MD+HTML Reader is available here: &lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/read-ai-markdown-html-on-mac/" rel="noopener noreferrer"&gt;How to review AI-generated Markdown and HTML on Mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/tools/meta-tag-checker/" rel="noopener noreferrer"&gt;Meta Tag Checker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution" rel="noopener noreferrer"&gt;Apple notarization guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://v2.tauri.app/plugin/updater/" rel="noopener noreferrer"&gt;Tauri updater plugin documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Building a shippable Record &amp; Replay app for macOS</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 12:32:13 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/building-a-shippable-record-replay-app-for-macos-3b6b</link>
      <guid>https://dev.to/ahab_indieseek/building-a-shippable-record-replay-app-for-macos-3b6b</guid>
      <description>&lt;p&gt;Codex Record &amp;amp; Replay points to a product idea I find very interesting: sometimes users do not want to explain a workflow from scratch. They already know how to do the work. They just want to show it once and turn that demonstration into something reusable.&lt;/p&gt;

&lt;p&gt;That is a strong idea. But a standalone product cannot be a simple browser recorder or a mouse-coordinate macro. If people are going to install it, trust it, and use it repeatedly, it has to record the real browser and the real desktop, protect sensitive data, and replay the intent of the workflow instead of blindly replaying pixels.&lt;/p&gt;

&lt;p&gt;The product I would build is a local-first Mac app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record a browser + desktop workflow
-&amp;amp;gt; review what was captured
-&amp;amp;gt; compile it into an editable routine
-&amp;amp;gt; replay it with verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage over Codex is not “a better general agent.” The advantage is product ownership: local storage, bring-your-own-model settings, a routine library, run history, scheduling, logs, and exportable workflow files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core product bet
&lt;/h2&gt;

&lt;p&gt;A useful Record &amp;amp; Replay app should turn one demonstration into a routine that can run again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Demonstrate
-&amp;amp;gt; capture evidence
-&amp;amp;gt; compile a routine
-&amp;amp;gt; run the routine
-&amp;amp;gt; verify the result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important word is “compile.” Raw events are evidence. They are not the final automation.&lt;/p&gt;

&lt;p&gt;If the user clicks a button, the product should not remember only &lt;code&gt;x=1040, y=72&lt;/code&gt;. It should remember the app, the window, the accessible target, the surrounding text, the browser DOM, a screenshot if needed, and the reason that step mattered.&lt;/p&gt;

&lt;p&gt;That is the difference between a useful workflow system and a fragile macro recorder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first version must cover browser and desktop
&lt;/h2&gt;

&lt;p&gt;Browser-only automation is useful, but it is not enough for this product.&lt;/p&gt;

&lt;p&gt;Real workflows often cross boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open a page in Chrome;&lt;/li&gt;
&lt;li&gt;download a file;&lt;/li&gt;
&lt;li&gt;find it in Finder;&lt;/li&gt;
&lt;li&gt;upload it somewhere else;&lt;/li&gt;
&lt;li&gt;copy a value from Notes or Slack;&lt;/li&gt;
&lt;li&gt;confirm a macOS dialog;&lt;/li&gt;
&lt;li&gt;return to the browser and submit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the product can only see the browser, it misses the part that makes Record &amp;amp; Replay different.&lt;/p&gt;

&lt;p&gt;So the first credible version needs two surfaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  User["User demonstrates workflow"] --&amp;amp;gt; App["Mac app"]
  App --&amp;amp;gt; Browser["Browser surface\nextension + native messaging"]
  App --&amp;amp;gt; Desktop["Desktop surface\nAccessibility + input events + screenshots"]
  Browser --&amp;amp;gt; Trace["Local trace\nsession.json\nevents.jsonl\nkeyframes"]
  Desktop --&amp;amp;gt; Trace
  Trace --&amp;amp;gt; Review["Review and redaction"]
  Review --&amp;amp;gt; Compile["Routine compiler"]
  Compile --&amp;amp;gt; Routine["workflow.json\nroutine.md\nassets"]
  Routine --&amp;amp;gt; Runtime["Replay runtime"]
  Runtime --&amp;amp;gt; Verify["Verification"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser surface should capture URL, title, DOM target, ARIA role/name, selector candidates, input changes, navigation, and key screenshots.&lt;/p&gt;

&lt;p&gt;The desktop surface should capture foreground app, window title, Accessibility role/name/value, focus, selection, input events, and screenshots.&lt;/p&gt;

&lt;p&gt;Both should write into one timeline. The routine compiler should see the workflow as one story, not as two disconnected logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace is evidence, not the product
&lt;/h2&gt;

&lt;p&gt;The trace format should be boring and explicit.&lt;/p&gt;

&lt;p&gt;Each event needs to say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what surface it came from;&lt;/li&gt;
&lt;li&gt;what app and window were active;&lt;/li&gt;
&lt;li&gt;what the user did;&lt;/li&gt;
&lt;li&gt;what target was involved;&lt;/li&gt;
&lt;li&gt;what context was captured;&lt;/li&gt;
&lt;li&gt;which fields are redacted;&lt;/li&gt;
&lt;li&gt;which screenshot, DOM, or Accessibility snapshot supports the event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple event might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eventId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evt_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chrome"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"windowTitle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[aria-label='Search']"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"redacted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice 2026"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contextRefs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dom_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"frame_001"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product should not store everything as plain text forever. Raw content, summaries, screenshots, and model-bound context should be separated. Before anything leaves the device, the user should be able to see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compiler is the real product
&lt;/h2&gt;

&lt;p&gt;The compiler turns noisy evidence into an editable routine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
  Raw["Raw events"] --&amp;amp;gt; Clean["Clean noise"]
  Clean --&amp;amp;gt; Segment["Segment steps"]
  Segment --&amp;amp;gt; Anchor["Build stable anchors"]
  Anchor --&amp;amp;gt; Params["Detect variables"]
  Params --&amp;amp;gt; Verify["Write verification"]
  Verify --&amp;amp;gt; Routine["routine.md + workflow.json"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good compilation means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;merge typing into one input step;&lt;/li&gt;
&lt;li&gt;remove accidental clicks and idle time;&lt;/li&gt;
&lt;li&gt;detect which values change between runs;&lt;/li&gt;
&lt;li&gt;prefer semantic targets over coordinates;&lt;/li&gt;
&lt;li&gt;write verification for important steps;&lt;/li&gt;
&lt;li&gt;mark risks and sensitive fields;&lt;/li&gt;
&lt;li&gt;ask the user only when intent is unclear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good routine should read like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the report page.
Choose the date range.
Upload the selected file.
Submit the draft.
Verify that the success message appears.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Move mouse to 1040,72.
Click.
Wait 500ms.
Press Tab.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Replay needs a verification loop
&lt;/h2&gt;

&lt;p&gt;Replay should be a state machine, not a script that blindly runs line by line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stateDiagram-v2
  [*] --&amp;amp;gt; LoadRoutine
  LoadRoutine --&amp;amp;gt; CollectInputs
  CollectInputs --&amp;amp;gt; Preflight
  Preflight --&amp;amp;gt; ExecuteStep
  ExecuteStep --&amp;amp;gt; VerifyStep
  VerifyStep --&amp;amp;gt; ExecuteStep: Pass and more steps remain
  VerifyStep --&amp;amp;gt; Recover: Fail
  Recover --&amp;amp;gt; ExecuteStep: Recovered
  Recover --&amp;amp;gt; HumanTakeover: Needs help
  HumanTakeover --&amp;amp;gt; ExecuteStep: User resumes
  VerifyStep --&amp;amp;gt; Done: All steps pass
  Done --&amp;amp;gt; [*]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime should try tools in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the most stable semantic path first: API, MCP, Apple Events, browser DOM action, or Accessibility action.&lt;/li&gt;
&lt;li&gt;Use structure next: selector, ARIA label, visible text, Accessibility role, or window hierarchy.&lt;/li&gt;
&lt;li&gt;Use visual matching when structure is incomplete.&lt;/li&gt;
&lt;li&gt;Use coordinates only as a last resort, tied to a known window and screenshot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dangerous actions need explicit confirmation. Deleting data, submitting payments, changing passwords, uploading personal files, installing software, or sending sensitive information should not run unattended.&lt;/p&gt;

&lt;h2&gt;
  
  
  BYOK is a real reason to exist
&lt;/h2&gt;

&lt;p&gt;Bring-your-own-model support is not just a settings feature. It is a product advantage.&lt;/p&gt;

&lt;p&gt;It lets users choose cost, privacy, and model quality. The app should support at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI-compatible base URL + API key + model;&lt;/li&gt;
&lt;li&gt;Anthropic;&lt;/li&gt;
&lt;li&gt;Gemini;&lt;/li&gt;
&lt;li&gt;OpenRouter;&lt;/li&gt;
&lt;li&gt;local models through Ollama or a compatible server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keys should live in Keychain. The settings screen should include connection tests, model capability hints, context preview, and rough cost estimates.&lt;/p&gt;

&lt;p&gt;Cheap models can clean text and summarize traces. Stronger multimodal models can handle ambiguous screens or visual recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution is part of the plan
&lt;/h2&gt;

&lt;p&gt;This kind of app should start outside the Mac App Store.&lt;/p&gt;

&lt;p&gt;It needs Accessibility, Screen Recording, Input Monitoring, Apple Events in some cases, helper processes, and browser extension setup. Developer ID signing and notarization are the more realistic first route.&lt;/p&gt;

&lt;p&gt;The user experience also has to explain permissions clearly. A workflow recorder sees a lot. If the app cannot explain what it records, where it stores data, and what it sends to models, it will not deserve trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  A realistic v1
&lt;/h2&gt;

&lt;p&gt;The first sellable version should be narrow, but complete.&lt;/p&gt;

&lt;p&gt;It should support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome or Brave workflow recording;&lt;/li&gt;
&lt;li&gt;macOS app/window tracking and Accessibility targets;&lt;/li&gt;
&lt;li&gt;one local trace timeline;&lt;/li&gt;
&lt;li&gt;a trace review screen with deletion and redaction;&lt;/li&gt;
&lt;li&gt;routine compilation into &lt;code&gt;routine.md&lt;/code&gt; and &lt;code&gt;workflow.json&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;browser + desktop replay with verification;&lt;/li&gt;
&lt;li&gt;human takeover when the app is unsure;&lt;/li&gt;
&lt;li&gt;local-first storage and context preview;&lt;/li&gt;
&lt;li&gt;signed and notarized distribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good first workflows are boring on purpose: back-office browser forms, browser + Finder upload/download flows, and simple native app steps in Finder, Notes, Mail, Calendar, or Slack.&lt;/p&gt;

&lt;p&gt;I would avoid banking, payments, government, medical, games, complex design tools, and fully unattended sensitive actions in v1.&lt;/p&gt;

&lt;h2&gt;
  
  
  My final take
&lt;/h2&gt;

&lt;p&gt;This is a viable product direction if the positioning is honest.&lt;/p&gt;

&lt;p&gt;It is not “automate every app.”&lt;br&gt;&lt;br&gt;
It is not “record pixels and replay them.”&lt;br&gt;&lt;br&gt;
It is not “browser automation with a nicer UI.”&lt;/p&gt;

&lt;p&gt;The stronger promise is:&lt;/p&gt;

&lt;p&gt;&amp;gt; Record a browser + Mac workflow once, turn it into an editable AI routine, and replay it with your own model.&lt;/p&gt;

&lt;p&gt;That gives the product a clear reason to exist next to Codex: local-first control, model choice, portable routines, and a UI built for repeated real-world runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI Codex Record &amp;amp; Replay: &lt;/li&gt;
&lt;li&gt;OpenAI Codex Computer Use: &lt;/li&gt;
&lt;li&gt;Captr for macOS: &lt;/li&gt;
&lt;li&gt;OpenBrowser: &lt;/li&gt;
&lt;li&gt;Interceptor: &lt;/li&gt;
&lt;li&gt;workflow-use: &lt;/li&gt;
&lt;li&gt;Apple notarization: &lt;/li&gt;
&lt;li&gt;Apple ScreenCaptureKit: &lt;/li&gt;
&lt;li&gt;Apple AXUIElement: &lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/codex-record-and-replay-principles/" rel="noopener noreferrer"&gt;Codex Record &amp;amp; Replay principles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/md-html-reader/" rel="noopener noreferrer"&gt;MD+HTML Reader product page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Building Paste Switch: from a small clipboard itch to a focused macOS app</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 12:32:12 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/building-paste-switch-from-a-small-clipboard-itch-to-a-focused-macos-app-15i3</link>
      <guid>https://dev.to/ahab_indieseek/building-paste-switch-from-a-small-clipboard-itch-to-a-focused-macos-app-15i3</guid>
      <description>&lt;p&gt;Paste Switch started with a small annoyance.&lt;/p&gt;

&lt;p&gt;When I work on a Mac, I often copy several things in a short burst: a command, a URL, a sentence, an image, a note, another command. Clipboard managers can store all of that, but most of the time I do not want to open a big history window, search, pick an item, and go back.&lt;/p&gt;

&lt;p&gt;I wanted something faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;copy a few things
press one shortcut
paste the latest item
press the same shortcut again if I wanted the previous one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the whole idea. Stay in the app where I am typing, and switch recent clipboard items without opening a manager.&lt;/p&gt;

&lt;p&gt;The first name was PasteCycle. It described the action, but it sounded more like a feature. The product became &lt;a href="https://indieseek.co/apps/paste-switch/" rel="noopener noreferrer"&gt;Paste Switch&lt;/a&gt;: a small Mac menu bar utility for switching recent clipboard items into the place where I am already working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The important decision was what not to build
&lt;/h2&gt;

&lt;p&gt;There are already powerful clipboard managers. They have search, folders, favorites, sync, rules, rich previews, and long history.&lt;/p&gt;

&lt;p&gt;Paste Switch is not trying to be that.&lt;/p&gt;

&lt;p&gt;Its job is narrower:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;watch recent clipboard items
-&amp;gt; keep a small local history
-&amp;gt; paste the newest item
-&amp;gt; press again to replace it with the next one
-&amp;gt; get out of the way
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boundary changed the engineering. History could stay small. The panel could be secondary. The main experience had to be the shortcut.&lt;/p&gt;

&lt;p&gt;The question was not “can I store everything the user copies?” The better question was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can I paste the wrong recent item, press the same shortcut again, and quickly get the right one?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that works, the product has a reason to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MVP was the shortcut loop
&lt;/h2&gt;

&lt;p&gt;The tempting thing was to design a beautiful clipboard panel first. I still needed a panel, but the product would be judged by the shortcut.&lt;/p&gt;

&lt;p&gt;The first loop was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;copy A
copy B
press Shift+Cmd+V -&amp;gt; paste B
press Shift+Cmd+V again -&amp;gt; replace B with A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks simple. It is not simple in real apps.&lt;/p&gt;

&lt;p&gt;Pasting is easy. Safe replacement is the hard part. After the first paste, the user might type more text, move the cursor, switch apps, or change the selection. At that point, blindly undoing and pasting something else would be dangerous.&lt;/p&gt;

&lt;p&gt;So Paste Switch keeps replacement inside one active cycle. While the cycle is active, repeated shortcut presses can undo the previous Paste Switch paste and paste the next item. If unrelated keyboard input happens, the cycle ends.&lt;/p&gt;

&lt;p&gt;That keeps the feature from becoming too aggressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  macOS permissions are part of the UX
&lt;/h2&gt;

&lt;p&gt;Paste Switch has to send native paste and undo events into the focused app. That means macOS Accessibility permission is not a small implementation detail. It is part of the product experience.&lt;/p&gt;

&lt;p&gt;Later, the smoother cycle interaction also needed Input Monitoring. With that permission, the app can know when Command or Shift is released and end the cycle immediately. Without it, the interaction cannot be as precise.&lt;/p&gt;

&lt;p&gt;The permission UI had to explain this without sounding scary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessibility lets Paste Switch paste and replace content in the focused app.&lt;/li&gt;
&lt;li&gt;Input Monitoring lets it end a cycle when the shortcut modifiers are released.&lt;/li&gt;
&lt;li&gt;No private data is collected or uploaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a utility like this, trust is not separate from functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The overlay should be feedback, not a picker
&lt;/h2&gt;

&lt;p&gt;Shortcut-only switching can feel invisible. The app needs to show what just happened, but it should not pull the user into a full picker.&lt;/p&gt;

&lt;p&gt;That is why Paste Switch has a small overlay. It appears near the active input position, shows the current item, and updates when the user presses the shortcut again.&lt;/p&gt;

&lt;p&gt;The overlay has one job: make the shortcut feel understandable while keeping focus in the target app.&lt;/p&gt;

&lt;p&gt;This small UI created a surprising number of details: modifier-key timing, multi-monitor positioning, full-screen apps, input-position detection, overlay height, hover behavior, and when to hide. None of those details are glamorous, but they decide whether the app feels crisp or broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The menu bar shape fits the product
&lt;/h2&gt;

&lt;p&gt;Paste Switch is not something I want to open all day. It should be running quietly.&lt;/p&gt;

&lt;p&gt;So the app became a menu bar utility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no Dock-first workflow;&lt;/li&gt;
&lt;li&gt;status lives in the menu bar;&lt;/li&gt;
&lt;li&gt;clicking the icon opens the compact panel;&lt;/li&gt;
&lt;li&gt;losing focus hides the panel;&lt;/li&gt;
&lt;li&gt;right-click can quit the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The panel still matters. It shows recent clips, permissions, shortcut state, reset, clear, and Pro status. But it supports the main action. It is not the main action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it a product added another layer
&lt;/h2&gt;

&lt;p&gt;Once the shortcut loop worked, the next question was whether it could become something another person could try.&lt;/p&gt;

&lt;p&gt;That meant adding the less exciting parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stable app identity and icon;&lt;/li&gt;
&lt;li&gt;trial and Pro state;&lt;/li&gt;
&lt;li&gt;monthly and lifetime payment links;&lt;/li&gt;
&lt;li&gt;license activation;&lt;/li&gt;
&lt;li&gt;update checks;&lt;/li&gt;
&lt;li&gt;tests for UI and cycle logic;&lt;/li&gt;
&lt;li&gt;signed local builds and DMG packaging;&lt;/li&gt;
&lt;li&gt;a product page and download flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same lesson I keep relearning: the core feature is only one part of a product. The path around it matters just as much.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would keep
&lt;/h2&gt;

&lt;p&gt;The best decision was keeping the product narrow.&lt;/p&gt;

&lt;p&gt;If I had started with “build a better clipboard manager,” the scope would have exploded. Search, folders, sync, rich formats, permanent history, import/export, rules, and power-user settings would all seem necessary.&lt;/p&gt;

&lt;p&gt;By starting with one repeated action, the product stayed clear.&lt;/p&gt;

&lt;p&gt;Copy a few things. Stay in the app where you are working. Press the shortcut until the right item lands.&lt;/p&gt;

&lt;p&gt;That is a small enough product to build, and a useful enough workflow to keep polishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and related resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/apps/paste-switch/" rel="noopener noreferrer"&gt;Paste Switch product page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieseek.co/blogs/macos-input-caret-position-ai-agent/" rel="noopener noreferrer"&gt;How to locate the input position on macOS for an AI-assisted app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/appkit/nspasteboard" rel="noopener noreferrer"&gt;Apple NSPasteboard documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/accessibility" rel="noopener noreferrer"&gt;Apple Accessibility documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>tools</category>
      <category>startup</category>
      <category>programming</category>
    </item>
    <item>
      <title>大家好</title>
      <dc:creator>Ahab</dc:creator>
      <pubDate>Sun, 05 Jul 2026 06:59:36 +0000</pubDate>
      <link>https://dev.to/ahab_indieseek/da-jia-hao-2bcm</link>
      <guid>https://dev.to/ahab_indieseek/da-jia-hao-2bcm</guid>
      <description>&lt;p&gt;大家好&lt;/p&gt;

</description>
      <category>introduction</category>
    </item>
  </channel>
</rss>
