πŸ“— Vibelog

Well Read - A Fable Moment

Today, I had what I could probably describe as a true “Fable” moment. I was asking Claude Code to make some changes to Well Read (yes, I’m still using it) when I considered throwing it a problem that Opus was unable to solve. This Flutter app renders a post within an embedded WebView, and since day one, this WebView had some pretty janky scrolling. For small pages it was fine, but as it grew, or when more images were present, the refresh rate will drop like a stone.

I had Opus take a look at this a while ago. It suggested a few minor things like adjusting the styling, but none of these seemed to helped. I have had Fable look at tasks that Opus did poorly in the past, I have heard others say that Fable was capable of solving problems that prior models had trouble with. It seemed to work for them, and since I had Fable open doing other things, I wondered if it could work for me. And besides, this scrolling jank was really bugging me. If Fable couldn’t solve it, ah well, I’m left with what I have. But if it could…

So here was my prompt to Fable:

I’m experiencing issues when browsing pages within the web-viewer. Scrolling is particularly choppy when there are images or when the article is too long. It would be nice if the scrolling was smoother. Is there some way in which this could be improved?

Short and sweet. Here was its response:

Fable’s diagnosis of the issue

Good question β€” and I can point at some likely culprits in the reader’s code itself. This is an assessment; nothing’s changed yet. The honest caveat up front: scroll jank can only truly be diagnosed on the device (I can’t reproduce it in this VM), so these are ranked by likelihood based on reading post_reader_screen.dart, not by measurement.

  1. The content-visibility CSS in the injected HTML is doing nothing useful and possibly harm. The article body is wrapped in a single <div class=“content”> styled with content-visibility: auto; contain-intrinsic-size: 0 2000px; (post_reader_screen.dart:179-182). That CSS feature only helps when applied to many small sections so the engine can skip rendering the off-screen ones β€” on one wrapper div it renders everything the moment any part is visible, and the 2000px size guess can cause layout jumps and scrollbar weirdness on long articles. Removing it (or applying it to the article’s child elements instead) is a two-line change.

  2. Images have no lazy loading, so every image in the article loads and decodes eagerly. Feed HTML arrives without loading=“lazy” or decoding=“async” attributes, and JavaScript is disabled in the WebView so nothing adds them. Decoding several large images while you scroll is a classic source of exactly the choppiness you describe. Since the app already assembles the HTML string, it could rewrite <img tags to add those attributes before loading β€” both work in the Android WebView engine even with JS disabled. Images also arrive without dimensions, so the page reflows as each one loads; that’s harder to fix fully, but lazy loading confines it to the part you’re actually reading.

  3. The platform-view rendering mode. The WebView renders through Flutter’s platform-view compositing, and Android has two modes with very different scroll-performance profiles (texture-layer vs. hybrid composition). The app currently uses the default. Toggling displayWithHybridComposition in the WebViewWidget creation params is a small experiment that sometimes transforms scroll smoothness on particular devices β€” worth trying if 1 and 2 aren’t enough.

  4. The bigger option: drop the WebView for native rendering with something like flutter_widget_from_html. Scrolling becomes a native Flutter list β€” generally the smoothest outcome and it would also fix the WebView’s other quirks β€” but it’s a real feature change with its own rendering trade-offs (complex embeds, tables, code blocks may render differently), so I’d only go there if the cheap fixes disappoint.

My recommendation: try 1 + 2 first β€” they’re small, low-risk, and directly target “choppy with images” and “choppy when long”. Then evaluate on your device, and escalate to 3 or 4 only if needed. Want me to design and implement those first two?

Option 2 and 4 were no-gos, and option 1 is essentially undoing an attempted fix by Opus, which was amusing. But what looked interesting was option 3, which Opus never brought up. I ended up going with that and option 1, and it seemed to have done the job. Scrolling is… well, I wouldn’t say it’s buttery smooth, but it’s significantly improved. I’ve been trying it for a few hours and I’m no longer experiencing any jank.

So the stories of Fable being a more capable model are definitely true.

As for the rest of the tool, it’s coming along. I’ve asked Claude to clean up the handling of “Mark as Read” in the feed viewer. Prior to today, opening an item would start a 1 second timer before marking the item as read. Moving away prior to the timer timing-out would cancel this. This was functionality I requested, as it was a way of allowing me to quickly “peak” at an item without changing its read status. But this delay turned out to be more trouble than it’s worth though. If you time your actions right, you can actually hit the “Mark as Read” button at the same time the app tries to change the read status itself. This induces a conflict of some sort, and the screen goes blank, requiring a restart of the app. Now, items are marked to be read immediately.

But the biggest change was adding a way to make a Micro.blog post about the item from within the app. This was lifted wholesale from Inkwell, and supports features like pre-populating the link with the page title, and including any selected text in the quote as a block quote. It’s also possible to set a prefix that’ll appear just before the link (I usually use the link emoji for this) and a post category. And despite some funky selection behaviour in the web-view, it works well enough.

Auto-generated description: Three smartphone screens show a note-taking app with a text about the EP-1320 Medieval, a post creation interface with typed text, and the app settings for posting to a blog.
From left to right: the "New Post" menu item, the post authoring screen, and the settings screen.

Oh, and I would say it’s still pretty amazing being able to vibe-code most of this directly from my phone. The agent does the work, then pushes it to Forejo as a PR, which kicks off a build of an APK, which I can download and install directly onto my phone. I’m not looking forward to Google’s locking down this form of side-loading in any capacity.