{
  "version": "https://jsonfeed.org/version/1",
  "title": "programming on jocmp",
  "icon": "https://avatars.micro.blog/avatars/2026/14/52207.jpg",
  "home_page_url": "https://jocmp.com/",
  "feed_url": "https://jocmp.com/feed.json",
  "items": [
      {
        "id": "http://jocmp.micro.blog/2026/01/19/mercury-parser/",
        "title": "Mercury Parser 3.0.0",
        "content_html": "<p>In 2024 I forked <a href=\"https://github.com/postlight/parser\">Postlight Parser</a> to use in <a href=\"https://capyreader.com/\">Capy Reader</a> and returned the name to <a href=\"https://github.com/jocmp/mercury-parser\">Mercury Parser</a>. I really, really wanted to use the upstream version of the parser to avoid maintaining something off to the side. But nearly two years on, I feel I made the right choice for the scope of the app.</p>\n<p>That&rsquo;s why I&rsquo;m happy to announce <a href=\"https://github.com/jocmp/mercury-parser/releases/tag/v3.0.0\">Mercury Parser version 3.0</a>. My fork of this project follows romantic versioning, or <a href=\"https://github.com/romversioning/romver\">romver</a> meaning that version 3 represents a major overhaul to how the project is developed.</p>\n<p>For starters, the app no longer uses jQuery. The previous version of the app used jQuery for the web-based version of the parser. This was completely removed by <a href=\"https://github.com/jocmp/mercury-parser/pull/88\">upgrading Cheerio</a> to the latest version. Cheerio is responsible for much of the parsing in Mercury Parser and follows a similar interface to jQuery, just without the jQuery.</p>\n<p>Next, I replaced moment.js with <a href=\"https://github.com/iamkun/dayjs\">dayjs</a> for article date handling. This was another necessary shift since moment.js had been deprecated for several years. The shift was mostly one-to-one but there were a few trade-offs. Date patterns like <code>DD</code> are now replaced by <code>D</code> which was something lenient in moment.js. Date boundaries and timezone suffixes were also changed. This required manual code in the <a href=\"https://github.com/jocmp/mercury-parser/blob/4e4ebcab3f4251512e746b4fe7b80a10b0da5dd8/src/cleaners/date-published.js#L22\">date-published</a> cleaner.</p>\n<p>I also migrated from Karma for web tests to <a href=\"https://vitest.dev/\">Vitest</a> with Playwright. For now this means that the fixtures are only tested in node due to the constraint of the node-only <code>fs</code> import. This is something I may revisit in future for broader test coverage. Lastly, I <a href=\"https://github.com/jocmp/mercury-parser/pull/86\">migrated</a> the project from yarn v1 to npm. There&rsquo;s safety in defaults, and npm has come along way with performance since Mercury Parser launched in 2016.</p>\n<p>All in all, these changes have been easy to manage because my fork of Mercury Parser is a hobby project. There&rsquo;s no stakeholders, no mission critical applications. That, and Claude Code Opus 4.5 is just leagues better than me at catching package conflicts. It&rsquo;s been a fun journey to continue to revitalize this project in my spare time and use it in Capy Reader.</p>\n<hr>\n<p>If you use this fork and have feedback, let me know! If you use the full content extractor in Capy Reader and enjoy its benefits, consider sponsoring my efforts <a href=\"https://github.com/sponsors/jocmp\">on GitHub</a> or <a href=\"https://ko-fi.com/capyreader\">Ko-fi</a>.</p>\n",
        "date_published": "2026-01-19T16:48:11-05:00",
        "url": "https://jocmp.com/2026/01/19/mercury-parser/",
        "tags": ["programming","mercury parser"]
      },
      {
        "id": "http://jocmp.micro.blog/2025/08/08/demo-mode-for-ios-simulators/",
        "title": "Demo mode for iOS simulators",
        "content_html": "<p>I was in search of a better way to take app screenshots. Typically you have two choices: use a physical device or a simulator. Physical devices are fine for a realistic display, but you can&rsquo;t control the time or the radio indicators. No one wants to see just two bars of service on a screencap. iOS&rsquo; default simulators don&rsquo;t even show the service indicator.</p>\n<p>Luckily the first result I came across was <a href=\"https://www.jessesquires.com/blog/2019/09/26/overriding-status-bar-settings-ios-simulator/\">a post from Jesse Squires</a> from 2019. The following command will override the current simulator with full service and the iconic 9:41AM time.</p>\n<pre tabindex=\"0\"><code>xcrun simctl status_bar booted override --time &#34;9:41&#34; --dataNetwork &#34;wifi&#34; --wifiMode active --wifiBars 3 --cellularMode active --cellularBars 4 --batteryState charged --batteryLevel 100\n</code></pre><p>The end result looks like this.</p>\n<img src=\"https://cdn.uploads.micro.blog/238475/2025/demo-mode.png\">\n",
        "date_published": "2025-08-08T15:17:51-05:00",
        "url": "https://jocmp.com/2025/08/08/demo-mode-for-ios-simulators/",
        "tags": ["programming","mobile development","ios"]
      },
      {
        "id": "http://jocmp.micro.blog/2025/08/02/capy-log-open-in-browser/",
        "title": "Capy Log: Open In Browser, Article List Tweaks",
        "content_html": "<p>Summer in the midwest is careening towards autumn, and version <a href=\"https://github.com/jocmp/capyreader/releases/tag/2025.08.1153\">2025.08.1153</a> of Capy Reader is in review for the Play Store. I haven&rsquo;t been adding huge feature changes lately. Instead, I&rsquo;ve been trying to pick apart the app and understand some underlying jank that exists. More on that later. While there aren&rsquo;t huge changes in this release, I did get around to adding &ldquo;Open in browser&rdquo; as an option to view articles.</p>\n<h2 id=\"open-in-browser\">Open In Browser</h2>\n<p>&ldquo;Open in browser&rdquo; by default has been a long outstanding feature. It was <a href=\"https://github.com/jocmp/capyreader/discussions/715\">first requested in January</a> of this year. I feel bad sometimes when there&rsquo;s a huge delta between time requested and time implemented, but I spent a lot of time waffling on this feature.</p>\n<p>It&rsquo;s deceptively simple but the &ldquo;open in browser&rdquo; requires two key expectations</p>\n<ol>\n<li>The article opens in the browser</li>\n<li>The article is marked as read</li>\n</ol>\n<p>This second expectation prevents a direct call to an <code>ACTION_VIEW</code> intent since there is not a way to simultaneously launch two actions from a widget RemoteView. The trade-off then is to create a <a href=\"https://marcellogalhardo.dev/posts/2023/trampoline-activities/\">trampoline activity</a> which first opens the app with a transparent view, and then opens the browser. The trampoline activity can be avoided when the &ldquo;open in browser&rdquo; action is initiated within the app since the article state is already within context.</p>\n<p>Putting it all together, the new &ldquo;open in browser&rdquo; feature in Capy does what it says on the tin:</p>\n<p><video controls=\"controls\" playsinline=\"playsinline\" src=\"https://cdn.uploads.micro.blog/238475/2025/demo.mp4\" poster=\"https://jocmp.com/uploads/2025/poster.png\" preload=\"none\"></video></p>\n<h2 id=\"feed-selection-ui-improvements\">Feed Selection UI Improvements</h2>\n<p>One outstanding problem in Capy Reader has been feed list transitions. To spot the bug in previous versions:</p>\n<ol>\n<li>Scroll part way down a page, note the status and toolbar change color</li>\n<li>Open the feed list and select another feed</li>\n<li>Jank! The previous article list will scroll - or teleport - to the first index, and then the next feed will load in</li>\n</ol>\n<p>Here&rsquo;s an example of the problem at 4x speed. The top bar state jank is noticeable at the 10 second mark when it still appears as the changed color when the list has already switched to the next feed.</p>\n<p><video src=\"https://cdn.uploads.micro.blog/238475/2025/before.mp4\" poster=\"https://jocmp.com/uploads/2025/650a7c3a16.png\" controls=\"controls\" preload=\"metadata\"></video></p>\n<p>The solution I found was to <a href=\"https://github.com/jocmp/capyreader/pull/1354/commits/1431818cbc8717816273bc427770127ea76e8458\">rebuild the article pager</a> in the view layer. This in turn invalidates the list state which simultaneously invalidates the scrollbar state.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"><code class=\"language-kotlin\" data-lang=\"kotlin\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">import</span> androidx.paging.Pager\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">import</span> androidx.paging.PagingConfig\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#75715e\">// In View Model\n</span></span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">fun</span> <span style=\"color:#a6e22e\">buildPager</span>(): Pager {\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#66d9ef\">return</span> Pager(\n</span></span><span style=\"display:flex;\"><span>        config = PagingConfig(\n</span></span><span style=\"display:flex;\"><span>            pageSize = <span style=\"color:#ae81ff\">50</span>,\n</span></span><span style=\"display:flex;\"><span>            prefetchDistance = <span style=\"color:#ae81ff\">10</span>,\n</span></span><span style=\"display:flex;\"><span>        ),\n</span></span><span style=\"display:flex;\"><span>        pagingSourceFactory = {\n</span></span><span style=\"display:flex;\"><span>            findArticles(<span style=\"color:#f92672\">..</span>.)\n</span></span><span style=\"display:flex;\"><span>        }\n</span></span><span style=\"display:flex;\"><span>    )\n</span></span><span style=\"display:flex;\"><span>}\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#75715e\">// Pager is now in Compose view layer\n</span></span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">val</span> articles = remember(<span style=\"color:#f92672\">..</span>.) {\n</span></span><span style=\"display:flex;\"><span>   viewModel.buildPager(<span style=\"color:#f92672\">..</span>.)\n</span></span><span style=\"display:flex;\"><span>}\n</span></span></code></pre></div><p>The last wrinkle with this approach is pager updates in response to in-feed actions. One example of this is swipe-to-refresh on the &ldquo;Unread&rdquo; filter. When the refresh finishes, articles that have been read disappear. If the invalidation flag - in this case a timestamp - is passed to the <code>remember</code> function then the feed will flash causing the list to flicker and jump back to the first index. Instead the solution is to track a separate variable within the view model. When the timestamp changes, the view-level <code>articles</code> variable <a href=\"https://github.com/jocmp/capyreader/pull/1365/files#diff-a3dc4554d8145d70c4d38aeebda102f8988a6794752336fe21357326112e00d7\">is refreshed</a> which triggers a new call to the <code>pagingSourceFactory</code>.</p>\n<p>Here&rsquo;s a demo putting it all together:</p>\n<p><video src=\"https://cdn.uploads.micro.blog/238475/2025/after.mp4\" poster=\"https://jocmp.com/uploads/2025/726aff4e1c.png\" controls=\"controls\" preload=\"metadata\"></video></p>\n<p>Something so subtle that you might not have even noticed, but your brain did.</p>\n<h2 id=\"whats-next\">What&rsquo;s next</h2>\n<p>In the next few updates I hope to add more settings around &ldquo;open in browser&rdquo; based on <a href=\"https://github.com/jocmp/capyreader/discussions/1306\">early feedback</a>. Additionally, I have an outstanding UI tweak around the <a href=\"https://github.com/jocmp/capyreader/discussions/1211\">local blocklists</a> that I really want to wrap up and deliver.</p>\n",
        "date_published": "2025-08-02T14:49:32-05:00",
        "url": "https://jocmp.com/2025/08/02/capy-log-open-in-browser/",
        "tags": ["capyreader","rss","programming"]
      },
      {
        "id": "http://jocmp.micro.blog/2025/07/12/full-content-extractors-comparing-defuddle/",
        "title": "Full Content Extractors: Comparing Defuddle and Postlight Parser",
        "content_html": "<p>One of the hardest problems with RSS feeds is displaying full content. It&rsquo;s essentially an unsolvable problem given the complexity of webpages and the lack of adherence to a semantic layout for any given blog or news site. It would be nice if each page had a header tag, and an article tag, but it&rsquo;s not that simple. Full content parsers attempt to solve this, but each has their own set of trade-offs.</p>\n<h2 id=\"legacy-contenders\">Legacy Contenders</h2>\n<p>Tools like Mozilla&rsquo;s Readability.js used to solve this in the past, but given <a href=\"https://www.theregister.com/2025/06/17/opinion_column_firefox/\">their recent woes</a> it&rsquo;s hard to trust it as a tool. <a href=\"https://github.com/postlight/parser\">Postlight Parser</a> née <a href=\"https://archive.postlight.com/insights/mercury-goes-open-source\">Mercury Parser</a> was a better option. Instead of trying to solve all webpages with a set of common heuristics, each domain could be overridden with a custom parser. In effect, The Verge could have a Verge-specific parser while Ars Technica could have a slightly different parser.</p>\n<p>It would be simple to stop there, but Postlight Parser was a product of its time. Around 2015, there was a <a href=\"https://www.theverge.com/23711172/google-amp-accelerated-mobile-pages-search-publishers-lawsuit\">push by Google</a> to speed up the web by simplifying webpages with AMP. Postlight was one of many companies that stepped in with their own set of tools like the parser to improve development with AMP. But as time passed, AMP fell out of fashion, and Postlight was <a href=\"https://archive.postlight.com/insights/postlight-joins-launch-by-ntt-data\">acquired by NTT Data</a>.</p>\n<p>Postlight Parser essentially ended with the acquisition. It&rsquo;s still possible to find Postlight Parser in the wild, however. Feedbin, a web-based feed reader, uses Postlight Parser to <a href=\"https://feedbin.com/blog/2019/03/11/the-future-of-full-content/\">power its full content mode</a>. Core development has ground to a halt with the last release <a href=\"https://github.com/postlight/parser/releases/tag/v2.2.3\">in 2022</a>.</p>\n<h2 id=\"a-new-entrant\">A New Entrant</h2>\n<p>Readability.js and Postlight Parser may very well represent the past of full content extraction. However a new project called Defuddle might take their place. <a href=\"https://github.com/kepano/defuddle\">Defuddle</a> was released in early 2025 by the developer behind the note-taking app Obsidian. It takes the Readability.js route of a one-size-fits-all input function with different internal heuristics.</p>\n<p>The following is a brief and non-exhaustive comparison between v2.2.3 of Postlight Parser and v0.6.4 of Defuddle using a small node.js application (<a href=\"https://github.com/jocmp/parser-comparison\">source code on GitHub</a>). Defuddle seems to work best when the site&rsquo;s markup is already well formatted which is the case with The Verge. In the <a href=\"https://www.theverge.com/24324299/asus-rog-zephyrus-g16-2024-gaming-laptop-review-amd-strix-point\">following review article</a>, Defuddle picks up more images, headers, and content like the overall review score than Postlight Parser.</p>\n<img src=\"https://cdn.uploads.micro.blog/238475/2025/parser-compare-verge.png\" width=\"600\" height=\"449\" alt=\"\">\n<p>Parsing fails if you throw <a href=\"https://sg.news.yahoo.com/mcdonald-pore-launches-chilli-crab-064000706.html\">an article from Yahoo News Singapore</a> at either Defuddle or Postlight Parser. Defuddle has a slight edge in that it at least extracts images and article content but still captures garbage text like &ldquo;ADVERTISEMENT.&rdquo;</p>\n<img src=\"https://cdn.uploads.micro.blog/238475/2025/parser-compare-yn-sg.png\" width=\"600\" height=\"449\" alt=\"\">\n<p>In short, better base markup still results in a better outcome. Defuddle is clearly the project to watch given Postlight Parser&rsquo;s lack of updates, and it&rsquo;s backed by a live project with Obsidian. Full content parsers come and go but the need to tame the chaos of the web is never ending.</p>\n",
        "date_published": "2025-07-12T18:03:00-05:00",
        "url": "https://jocmp.com/2025/07/12/full-content-extractors-comparing-defuddle/",
        "tags": ["rss","programming","mercury parser"]
      }
  ]
}
